mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 18:20:34 +00:00
linux: Support GDB remote serial protocol for x86_64
Add a flag '--gdb <port>' to provide GDB remote protocol interface so a developer can attach GDB to the guest kernel. In this CL, we support read/write operations for registers and memories. BUG=chromium:1141812 TEST=Attach gdb and see register values on workstation and intel DUT Change-Id: Ia07763870d94e87867f6df43f039196aa703ee59 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2440221 Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org> Tested-by: Keiichi Watanabe <keiichiw@chromium.org> Auto-Submit: Keiichi Watanabe <keiichiw@chromium.org> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
parent
fa149aba03
commit
c5262e9fad
14 changed files with 850 additions and 33 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -41,10 +41,12 @@ dependencies = [
|
||||||
"acpi_tables 0.1.0",
|
"acpi_tables 0.1.0",
|
||||||
"base 0.1.0",
|
"base 0.1.0",
|
||||||
"devices 0.1.0",
|
"devices 0.1.0",
|
||||||
|
"gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hypervisor 0.1.0",
|
"hypervisor 0.1.0",
|
||||||
"kernel_cmdline 0.1.0",
|
"kernel_cmdline 0.1.0",
|
||||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"minijail 0.2.1",
|
"minijail 0.2.1",
|
||||||
|
"msg_socket 0.1.0",
|
||||||
"resources 0.1.0",
|
"resources 0.1.0",
|
||||||
"sync 0.1.0",
|
"sync 0.1.0",
|
||||||
"vm_control 0.1.0",
|
"vm_control 0.1.0",
|
||||||
|
@ -160,6 +162,7 @@ dependencies = [
|
||||||
"devices 0.1.0",
|
"devices 0.1.0",
|
||||||
"disk 0.1.0",
|
"disk 0.1.0",
|
||||||
"enumn 0.1.0",
|
"enumn 0.1.0",
|
||||||
|
"gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"gpu_buffer 0.1.0",
|
"gpu_buffer 0.1.0",
|
||||||
"gpu_renderer 0.1.0",
|
"gpu_renderer 0.1.0",
|
||||||
"hypervisor 0.1.0",
|
"hypervisor 0.1.0",
|
||||||
|
@ -180,6 +183,7 @@ dependencies = [
|
||||||
"resources 0.1.0",
|
"resources 0.1.0",
|
||||||
"sync 0.1.0",
|
"sync 0.1.0",
|
||||||
"tempfile 3.0.7",
|
"tempfile 3.0.7",
|
||||||
|
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"vhost 0.1.0",
|
"vhost 0.1.0",
|
||||||
"vm_control 0.1.0",
|
"vm_control 0.1.0",
|
||||||
"vm_memory 0.1.0",
|
"vm_memory 0.1.0",
|
||||||
|
@ -375,6 +379,18 @@ dependencies = [
|
||||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gdbstub"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"managed 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"paste 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getopts"
|
name = "getopts"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
|
@ -540,6 +556,11 @@ dependencies = [
|
||||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "managed"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
@ -609,6 +630,14 @@ dependencies = [
|
||||||
"net_sys 0.1.0",
|
"net_sys 0.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -893,6 +922,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base 0.1.0",
|
"base 0.1.0",
|
||||||
"data_model 0.1.0",
|
"data_model 0.1.0",
|
||||||
|
"gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hypervisor 0.1.0",
|
"hypervisor 0.1.0",
|
||||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"msg_socket 0.1.0",
|
"msg_socket 0.1.0",
|
||||||
|
@ -931,11 +961,13 @@ dependencies = [
|
||||||
"base 0.1.0",
|
"base 0.1.0",
|
||||||
"data_model 0.1.0",
|
"data_model 0.1.0",
|
||||||
"devices 0.1.0",
|
"devices 0.1.0",
|
||||||
|
"gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hypervisor 0.1.0",
|
"hypervisor 0.1.0",
|
||||||
"kernel_cmdline 0.1.0",
|
"kernel_cmdline 0.1.0",
|
||||||
"kernel_loader 0.1.0",
|
"kernel_loader 0.1.0",
|
||||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"minijail 0.2.1",
|
"minijail 0.2.1",
|
||||||
|
"msg_socket 0.1.0",
|
||||||
"remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"resources 0.1.0",
|
"resources 0.1.0",
|
||||||
"sync 0.1.0",
|
"sync 0.1.0",
|
||||||
|
@ -960,12 +992,15 @@ dependencies = [
|
||||||
"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
|
"checksum futures-sink 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
|
||||||
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
|
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
|
||||||
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
|
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
|
||||||
|
"checksum gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "347c27d24b8ac4a2bcad3ff3d0695271a0510c020bd8134b53d189e973ed58bf"
|
||||||
"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
|
"checksum getopts 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
|
||||||
"checksum intrusive-collections 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bca8c0bb831cd60d4dda79a58e3705ca6eb47efb65d665651a8d672213ec3db"
|
"checksum intrusive-collections 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4bca8c0bb831cd60d4dda79a58e3705ca6eb47efb65d665651a8d672213ec3db"
|
||||||
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
|
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
|
||||||
"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
|
"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
|
||||||
|
"checksum managed 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
|
||||||
"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
|
"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
|
||||||
"checksum memoffset 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
|
"checksum memoffset 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
|
||||||
|
"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||||
"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
|
"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
|
||||||
"checksum paste 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba7ae1a2180ed02ddfdb5ab70c70d596a26dd642e097bb6fe78b1bde8588ed97"
|
"checksum paste 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba7ae1a2180ed02ddfdb5ab70c70d596a26dd642e097bb6fe78b1bde8588ed97"
|
||||||
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
|
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
|
||||||
|
|
|
@ -45,6 +45,7 @@ x = ["devices/x"]
|
||||||
virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
|
virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
|
||||||
composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
|
composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
|
||||||
gfxstream = ["devices/gfxstream"]
|
gfxstream = ["devices/gfxstream"]
|
||||||
|
gdb = ["gdbstub", "thiserror", "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arch = { path = "arch" }
|
arch = { path = "arch" }
|
||||||
|
@ -57,6 +58,7 @@ data_model = "*"
|
||||||
devices = { path = "devices" }
|
devices = { path = "devices" }
|
||||||
disk = { path = "disk" }
|
disk = { path = "disk" }
|
||||||
enumn = { path = "enumn" }
|
enumn = { path = "enumn" }
|
||||||
|
gdbstub = { version = "0.4.0", optional = true }
|
||||||
gpu_buffer = { path = "gpu_buffer", optional = true }
|
gpu_buffer = { path = "gpu_buffer", optional = true }
|
||||||
gpu_renderer = { path = "gpu_renderer", optional = true }
|
gpu_renderer = { path = "gpu_renderer", optional = true }
|
||||||
hypervisor = { path = "hypervisor" }
|
hypervisor = { path = "hypervisor" }
|
||||||
|
@ -77,6 +79,7 @@ remain = "*"
|
||||||
resources = { path = "resources" }
|
resources = { path = "resources" }
|
||||||
sync = { path = "sync" }
|
sync = { path = "sync" }
|
||||||
tempfile = "*"
|
tempfile = "*"
|
||||||
|
thiserror = { version = "1.0.20", optional = true }
|
||||||
vhost = { path = "vhost" }
|
vhost = { path = "vhost" }
|
||||||
vm_control = { path = "vm_control" }
|
vm_control = { path = "vm_control" }
|
||||||
acpi_tables = { path = "acpi_tables" }
|
acpi_tables = { path = "acpi_tables" }
|
||||||
|
|
27
README.md
27
README.md
|
@ -162,6 +162,33 @@ To use it, ensure that the `XDG_RUNTIME_DIR` enviroment variable is set and that
|
||||||
the path `$XDG_RUNTIME_DIR/wayland-0` points to the socket of the Wayland
|
the path `$XDG_RUNTIME_DIR/wayland-0` points to the socket of the Wayland
|
||||||
compositor you would like the guest to use.
|
compositor you would like the guest to use.
|
||||||
|
|
||||||
|
### GDB Support
|
||||||
|
|
||||||
|
crosvm supports [GDB Remote Serial Protocol] to allow developers to debug guest
|
||||||
|
kernel via GDB.
|
||||||
|
|
||||||
|
You can enable the feature by `--gdb` flag:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Use uncompressed vmlinux
|
||||||
|
$ crosvm run --gdb <port> ${USUAL_CROSVM_ARGS} vmlinux
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can start GDB in another shell.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ gdb vmlinux
|
||||||
|
(gdb) target remote :<port>
|
||||||
|
(gdb) c
|
||||||
|
<start booting in the other shell>
|
||||||
|
```
|
||||||
|
|
||||||
|
For general techniques for debugging the Linux kernel via GDB, see this
|
||||||
|
[kernel documentation].
|
||||||
|
|
||||||
|
[GDB Remote Serial Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
|
||||||
|
[kernel documentation]: https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
|
|
||||||
The following are crosvm's default arguments and how to override them.
|
The following are crosvm's default arguments and how to override them.
|
||||||
|
|
|
@ -4,13 +4,18 @@ version = "0.1.0"
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
gdb = ["gdbstub", "msg_socket"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acpi_tables = { path = "../acpi_tables" }
|
acpi_tables = { path = "../acpi_tables" }
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
|
gdbstub = { version = "0.4.0", optional = true }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
kernel_cmdline = { path = "../kernel_cmdline" }
|
kernel_cmdline = { path = "../kernel_cmdline" }
|
||||||
libc = "*"
|
libc = "*"
|
||||||
minijail = "*"
|
minijail = "*"
|
||||||
|
msg_socket = { path = "../msg_socket", optional = true }
|
||||||
resources = { path = "../resources" }
|
resources = { path = "../resources" }
|
||||||
sync = { path = "../sync" }
|
sync = { path = "../sync" }
|
||||||
base = { path = "../base" }
|
base = { path = "../base" }
|
||||||
|
|
|
@ -27,9 +27,15 @@ use hypervisor::{IoEventAddress, Vm};
|
||||||
use minijail::Minijail;
|
use minijail::Minijail;
|
||||||
use resources::{MmioType, SystemAllocator};
|
use resources::{MmioType, SystemAllocator};
|
||||||
use sync::Mutex;
|
use sync::Mutex;
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
use vm_control::VmControlRequestSocket;
|
||||||
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
|
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
|
||||||
use vm_control::BatteryType;
|
use vm_control::BatteryType;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
use gdbstub::arch::x86::reg::X86_64CoreRegs as GdbStubRegs;
|
||||||
|
|
||||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||||
use {
|
use {
|
||||||
devices::IrqChipAArch64 as IrqChipArch,
|
devices::IrqChipAArch64 as IrqChipArch,
|
||||||
|
@ -85,6 +91,8 @@ pub struct VmComponents {
|
||||||
pub acpi_sdts: Vec<SDT>,
|
pub acpi_sdts: Vec<SDT>,
|
||||||
pub rt_cpus: Vec<usize>,
|
pub rt_cpus: Vec<usize>,
|
||||||
pub protected_vm: bool,
|
pub protected_vm: bool,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
pub gdb: Option<(u32, VmControlRequestSocket)>, // port and control socket.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Holds the elements needed to run a Linux VM. Created by `build_vm`.
|
/// Holds the elements needed to run a Linux VM. Created by `build_vm`.
|
||||||
|
@ -105,6 +113,8 @@ pub struct RunnableLinuxVm<V: VmArch, Vcpu: VcpuArch, I: IrqChipArch> {
|
||||||
pub pid_debug_label_map: BTreeMap<u32, String>,
|
pub pid_debug_label_map: BTreeMap<u32, String>,
|
||||||
pub suspend_evt: Event,
|
pub suspend_evt: Event,
|
||||||
pub rt_cpus: Vec<usize>,
|
pub rt_cpus: Vec<usize>,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
pub gdb: Option<(u32, VmControlRequestSocket)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The device and optional jail.
|
/// The device and optional jail.
|
||||||
|
@ -174,6 +184,32 @@ pub trait LinuxArch {
|
||||||
has_bios: bool,
|
has_bios: bool,
|
||||||
no_smt: bool,
|
no_smt: bool,
|
||||||
) -> Result<(), Self::Error>;
|
) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
/// Reads vCPU's registers.
|
||||||
|
fn debug_read_registers<T: VcpuArch>(vcpu: &T) -> Result<GdbStubRegs, Self::Error>;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
/// Writes vCPU's registers.
|
||||||
|
fn debug_write_registers<T: VcpuArch>(vcpu: &T, regs: &GdbStubRegs) -> Result<(), Self::Error>;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
/// Reads bytes from the guest memory.
|
||||||
|
fn debug_read_memory<T: VcpuArch>(
|
||||||
|
vcpu: &T,
|
||||||
|
guest_mem: &GuestMemory,
|
||||||
|
vaddr: GuestAddress,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Vec<u8>, Self::Error>;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
/// Writes bytes to the specified guest memory.
|
||||||
|
fn debug_write_memory<T: VcpuArch>(
|
||||||
|
vcpu: &T,
|
||||||
|
guest_mem: &GuestMemory,
|
||||||
|
vaddr: GuestAddress,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Errors for device manager.
|
/// Errors for device manager.
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
//! configs.
|
//! configs.
|
||||||
|
|
||||||
pub mod argument;
|
pub mod argument;
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
pub mod gdb;
|
||||||
#[path = "linux.rs"]
|
#[path = "linux.rs"]
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
|
@ -215,6 +217,8 @@ pub struct Config {
|
||||||
pub acpi_tables: Vec<PathBuf>,
|
pub acpi_tables: Vec<PathBuf>,
|
||||||
pub protected_vm: bool,
|
pub protected_vm: bool,
|
||||||
pub battery_type: Option<BatteryType>,
|
pub battery_type: Option<BatteryType>,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
pub gdb: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -271,6 +275,8 @@ impl Default for Config {
|
||||||
acpi_tables: Vec::new(),
|
acpi_tables: Vec::new(),
|
||||||
protected_vm: false,
|
protected_vm: false,
|
||||||
battery_type: None,
|
battery_type: None,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
gdb: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
254
src/gdb.rs
Normal file
254
src/gdb.rs
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
// Copyright 2020 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
use std::net::TcpListener;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use base::{error, info};
|
||||||
|
use msg_socket::{MsgReceiver, MsgSender};
|
||||||
|
use sync::Mutex;
|
||||||
|
use vm_control::{
|
||||||
|
VcpuControl, VcpuDebug, VcpuDebugStatus, VcpuDebugStatusMessage, VmControlRequestSocket,
|
||||||
|
VmRequest, VmResponse,
|
||||||
|
};
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
|
use gdbstub::arch::Arch;
|
||||||
|
use gdbstub::target::ext::base::singlethread::{ResumeAction, SingleThreadOps, StopReason};
|
||||||
|
use gdbstub::target::ext::base::BaseOps;
|
||||||
|
use gdbstub::target::TargetError::NonFatal;
|
||||||
|
use gdbstub::target::{Target, TargetResult};
|
||||||
|
use gdbstub::Connection;
|
||||||
|
use remain::sorted;
|
||||||
|
use thiserror::Error as ThisError;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
use gdbstub::arch::x86::X86_64_SSE as GdbArch;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
type ArchUsize = u64;
|
||||||
|
|
||||||
|
pub fn gdb_thread(mut gdbstub: GdbStub, port: u32) {
|
||||||
|
let addr = format!("0.0.0.0:{}", port);
|
||||||
|
let listener = match TcpListener::bind(addr.clone()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to create a TCP listener: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!("Waiting for a GDB connection on {:?}...", addr);
|
||||||
|
|
||||||
|
let (stream, addr) = match listener.accept() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to accept a connection from GDB: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
info!("GDB connected from {}", addr);
|
||||||
|
|
||||||
|
let connection: Box<dyn Connection<Error = std::io::Error>> = Box::new(stream);
|
||||||
|
let mut gdb = gdbstub::GdbStub::new(connection);
|
||||||
|
|
||||||
|
match gdb.run(&mut gdbstub) {
|
||||||
|
Ok(reason) => {
|
||||||
|
info!("GDB session closed: {:?}", reason);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("error occurred in GDB session: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume the VM when GDB session is disconnected.
|
||||||
|
if let Err(e) = gdbstub.vm_request(VmRequest::Resume) {
|
||||||
|
error!("Failed to resume the VM after GDB disconnected: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[sorted]
|
||||||
|
#[derive(ThisError, Debug)]
|
||||||
|
enum Error {
|
||||||
|
/// Got an unexpected VM response.
|
||||||
|
#[error("Got an unexpected VM response: {0}")]
|
||||||
|
UnexpectedVmResponse(VmResponse),
|
||||||
|
/// Failed to send a vCPU request.
|
||||||
|
#[error("failed to send a vCPU request: {0}")]
|
||||||
|
VcpuRequest(mpsc::SendError<VcpuControl>),
|
||||||
|
/// Failed to receive a vCPU response.
|
||||||
|
#[error("failed to receive a vCPU response: {0}")]
|
||||||
|
VcpuResponse(mpsc::RecvTimeoutError),
|
||||||
|
/// Failed to send a VM request.
|
||||||
|
#[error("failed to send a VM request: {0}")]
|
||||||
|
VmRequest(msg_socket::MsgError),
|
||||||
|
/// Failed to receive a VM request.
|
||||||
|
#[error("failed to receive a VM response: {0}")]
|
||||||
|
VmResponse(msg_socket::MsgError),
|
||||||
|
}
|
||||||
|
type GdbResult<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub struct GdbStub {
|
||||||
|
vm_socket: Mutex<VmControlRequestSocket>,
|
||||||
|
vcpu_com: Vec<mpsc::Sender<VcpuControl>>,
|
||||||
|
from_vcpu: mpsc::Receiver<VcpuDebugStatusMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GdbStub {
|
||||||
|
pub fn new(
|
||||||
|
vm_socket: VmControlRequestSocket,
|
||||||
|
vcpu_com: Vec<mpsc::Sender<VcpuControl>>,
|
||||||
|
from_vcpu: mpsc::Receiver<VcpuDebugStatusMessage>,
|
||||||
|
) -> Self {
|
||||||
|
GdbStub {
|
||||||
|
vm_socket: Mutex::new(vm_socket),
|
||||||
|
vcpu_com,
|
||||||
|
from_vcpu,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vcpu_request(&self, request: VcpuControl) -> GdbResult<VcpuDebugStatus> {
|
||||||
|
// We use the only one vCPU when GDB is enabled.
|
||||||
|
self.vcpu_com[0].send(request).map_err(Error::VcpuRequest)?;
|
||||||
|
|
||||||
|
match self.from_vcpu.recv_timeout(Duration::from_millis(500)) {
|
||||||
|
Ok(msg) => Ok(msg.msg),
|
||||||
|
Err(e) => Err(Error::VcpuResponse(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vm_request(&self, request: VmRequest) -> GdbResult<()> {
|
||||||
|
let vm_socket = self.vm_socket.lock();
|
||||||
|
vm_socket.send(&request).map_err(Error::VmRequest)?;
|
||||||
|
match vm_socket.recv() {
|
||||||
|
Ok(VmResponse::Ok) => Ok(()),
|
||||||
|
Ok(r) => Err(Error::UnexpectedVmResponse(r)),
|
||||||
|
Err(e) => Err(Error::VmResponse(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target for GdbStub {
|
||||||
|
// TODO(keiichiw): Replace `()` with `X86_64CoreRegId` when we update the gdbstub crate.
|
||||||
|
type Arch = GdbArch<()>;
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn base_ops(&mut self) -> BaseOps<Self::Arch, Self::Error> {
|
||||||
|
BaseOps::SingleThread(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SingleThreadOps for GdbStub {
|
||||||
|
fn resume(
|
||||||
|
&mut self,
|
||||||
|
_action: ResumeAction,
|
||||||
|
check_gdb_interrupt: &mut dyn FnMut() -> bool,
|
||||||
|
) -> Result<StopReason<ArchUsize>, Self::Error> {
|
||||||
|
// TODO(keiichiw): Support step execution by checking `ResumeAction`.
|
||||||
|
|
||||||
|
self.vm_request(VmRequest::Resume).map_err(|e| {
|
||||||
|
error!("Failed to resume the target: {}", e);
|
||||||
|
"Failed to resume the target"
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Polling
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
|
if check_gdb_interrupt() {
|
||||||
|
self.vm_request(VmRequest::Suspend).map_err(|e| {
|
||||||
|
error!("Failed to suspend the target: {}", e);
|
||||||
|
"Failed to suspend the target"
|
||||||
|
})?;
|
||||||
|
return Ok(StopReason::GdbInterrupt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_registers(
|
||||||
|
&mut self,
|
||||||
|
regs: &mut <Self::Arch as Arch>::Registers,
|
||||||
|
) -> TargetResult<(), Self> {
|
||||||
|
match self.vcpu_request(VcpuControl::Debug(VcpuDebug::ReadRegs)) {
|
||||||
|
Ok(VcpuDebugStatus::RegValues(r)) => {
|
||||||
|
*regs = r;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(s) => {
|
||||||
|
error!("Unexpected vCPU response for ReadRegs: {:?}", s);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to request ReadRegs: {}", e);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_registers(
|
||||||
|
&mut self,
|
||||||
|
regs: &<Self::Arch as Arch>::Registers,
|
||||||
|
) -> TargetResult<(), Self> {
|
||||||
|
match self.vcpu_request(VcpuControl::Debug(VcpuDebug::WriteRegs(Box::new(
|
||||||
|
regs.clone(),
|
||||||
|
)))) {
|
||||||
|
Ok(VcpuDebugStatus::CommandComplete) => Ok(()),
|
||||||
|
Ok(s) => {
|
||||||
|
error!("Unexpected vCPU response for WriteRegs: {:?}", s);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to request WriteRegs: {}", e);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_addrs(
|
||||||
|
&mut self,
|
||||||
|
start_addr: <Self::Arch as Arch>::Usize,
|
||||||
|
data: &mut [u8],
|
||||||
|
) -> TargetResult<(), Self> {
|
||||||
|
match self.vcpu_request(VcpuControl::Debug(VcpuDebug::ReadMem(
|
||||||
|
GuestAddress(start_addr),
|
||||||
|
data.len(),
|
||||||
|
))) {
|
||||||
|
Ok(VcpuDebugStatus::MemoryRegion(r)) => {
|
||||||
|
for (dst, v) in data.iter_mut().zip(r.iter()) {
|
||||||
|
*dst = *v;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(s) => {
|
||||||
|
error!("Unexpected vCPU response for ReadMem: {:?}", s);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to request ReadMem: {}", e);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_addrs(
|
||||||
|
&mut self,
|
||||||
|
start_addr: <Self::Arch as Arch>::Usize,
|
||||||
|
data: &[u8],
|
||||||
|
) -> TargetResult<(), Self> {
|
||||||
|
match self.vcpu_request(VcpuControl::Debug(VcpuDebug::WriteMem(
|
||||||
|
GuestAddress(start_addr),
|
||||||
|
data.to_owned(),
|
||||||
|
))) {
|
||||||
|
Ok(VcpuDebugStatus::CommandComplete) => Ok(()),
|
||||||
|
Ok(s) => {
|
||||||
|
error!("Unexpected vCPU response for WriteMem: {:?}", s);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to request WriteMem: {}", e);
|
||||||
|
Err(NonFatal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
207
src/linux.rs
207
src/linux.rs
|
@ -62,13 +62,17 @@ use base::{
|
||||||
use vm_control::{
|
use vm_control::{
|
||||||
BalloonControlCommand, BalloonControlRequestSocket, BalloonControlResponseSocket,
|
BalloonControlCommand, BalloonControlRequestSocket, BalloonControlResponseSocket,
|
||||||
BalloonControlResult, DiskControlCommand, DiskControlRequestSocket, DiskControlResponseSocket,
|
BalloonControlResult, DiskControlCommand, DiskControlRequestSocket, DiskControlResponseSocket,
|
||||||
DiskControlResult, IrqSetup, UsbControlSocket, VmControlResponseSocket, VmIrqRequest,
|
DiskControlResult, IrqSetup, UsbControlSocket, VcpuControl, VmControlResponseSocket,
|
||||||
VmIrqRequestSocket, VmIrqResponse, VmIrqResponseSocket, VmMemoryControlRequestSocket,
|
VmIrqRequest, VmIrqRequestSocket, VmIrqResponse, VmIrqResponseSocket,
|
||||||
VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse, VmMsyncRequest,
|
VmMemoryControlRequestSocket, VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse,
|
||||||
VmMsyncRequestSocket, VmMsyncResponse, VmMsyncResponseSocket, VmRunMode,
|
VmMsyncRequest, VmMsyncRequestSocket, VmMsyncResponse, VmMsyncResponseSocket, VmRunMode,
|
||||||
};
|
};
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
use vm_control::{VcpuDebug, VcpuDebugStatus, VcpuDebugStatusMessage, VmRequest, VmResponse};
|
||||||
use vm_memory::{GuestAddress, GuestMemory};
|
use vm_memory::{GuestAddress, GuestMemory};
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
use crate::gdb::{gdb_thread, GdbStub};
|
||||||
use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption};
|
use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption};
|
||||||
use arch::{
|
use arch::{
|
||||||
self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VcpuAffinity,
|
self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VcpuAffinity,
|
||||||
|
@ -126,6 +130,8 @@ pub enum Error {
|
||||||
FsDeviceNew(virtio::fs::Error),
|
FsDeviceNew(virtio::fs::Error),
|
||||||
GetMaxOpenFiles(io::Error),
|
GetMaxOpenFiles(io::Error),
|
||||||
GetSignalMask(signal::Error),
|
GetSignalMask(signal::Error),
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
HandleDebugCommand(<Arch as LinuxArch>::Error),
|
||||||
InputDeviceNew(virtio::InputError),
|
InputDeviceNew(virtio::InputError),
|
||||||
InputEventsOpen(std::io::Error),
|
InputEventsOpen(std::io::Error),
|
||||||
InvalidFdPath,
|
InvalidFdPath,
|
||||||
|
@ -160,11 +166,15 @@ pub enum Error {
|
||||||
ResetTimer(base::Error),
|
ResetTimer(base::Error),
|
||||||
RngDeviceNew(virtio::RngError),
|
RngDeviceNew(virtio::RngError),
|
||||||
RunnableVcpu(base::Error),
|
RunnableVcpu(base::Error),
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
SendDebugStatus(Box<mpsc::SendError<VcpuDebugStatusMessage>>),
|
||||||
SettingGidMap(minijail::Error),
|
SettingGidMap(minijail::Error),
|
||||||
SettingMaxOpenFiles(minijail::Error),
|
SettingMaxOpenFiles(minijail::Error),
|
||||||
SettingSignalMask(base::Error),
|
SettingSignalMask(base::Error),
|
||||||
SettingUidMap(minijail::Error),
|
SettingUidMap(minijail::Error),
|
||||||
SignalFd(base::SignalFdError),
|
SignalFd(base::SignalFdError),
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
SpawnGdbServer(io::Error),
|
||||||
SpawnVcpu(io::Error),
|
SpawnVcpu(io::Error),
|
||||||
Timer(base::Error),
|
Timer(base::Error),
|
||||||
ValidateRawFd(base::Error),
|
ValidateRawFd(base::Error),
|
||||||
|
@ -222,6 +232,8 @@ impl Display for Error {
|
||||||
FsDeviceNew(e) => write!(f, "failed to create fs device: {}", e),
|
FsDeviceNew(e) => write!(f, "failed to create fs device: {}", e),
|
||||||
GetMaxOpenFiles(e) => write!(f, "failed to get max number of open files: {}", e),
|
GetMaxOpenFiles(e) => write!(f, "failed to get max number of open files: {}", e),
|
||||||
GetSignalMask(e) => write!(f, "failed to retrieve signal mask for vcpu: {}", e),
|
GetSignalMask(e) => write!(f, "failed to retrieve signal mask for vcpu: {}", e),
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
HandleDebugCommand(e) => write!(f, "failed to handle a gdb command: {}", e),
|
||||||
InputDeviceNew(e) => write!(f, "failed to set up input device: {}", e),
|
InputDeviceNew(e) => write!(f, "failed to set up input device: {}", e),
|
||||||
InputEventsOpen(e) => write!(f, "failed to open event device: {}", e),
|
InputEventsOpen(e) => write!(f, "failed to open event device: {}", e),
|
||||||
InvalidFdPath => write!(f, "failed parsing a /proc/self/fd/*"),
|
InvalidFdPath => write!(f, "failed parsing a /proc/self/fd/*"),
|
||||||
|
@ -263,11 +275,15 @@ impl Display for Error {
|
||||||
ResetTimer(e) => write!(f, "failed to reset Timer: {}", e),
|
ResetTimer(e) => write!(f, "failed to reset Timer: {}", e),
|
||||||
RngDeviceNew(e) => write!(f, "failed to set up rng: {}", e),
|
RngDeviceNew(e) => write!(f, "failed to set up rng: {}", e),
|
||||||
RunnableVcpu(e) => write!(f, "failed to set thread id for vcpu: {}", e),
|
RunnableVcpu(e) => write!(f, "failed to set thread id for vcpu: {}", e),
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
SendDebugStatus(e) => write!(f, "failed to send a debug status to GDB thread: {}", e),
|
||||||
SettingGidMap(e) => write!(f, "error setting GID map: {}", e),
|
SettingGidMap(e) => write!(f, "error setting GID map: {}", e),
|
||||||
SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
|
SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
|
||||||
SettingSignalMask(e) => write!(f, "failed to set the signal mask for vcpu: {}", e),
|
SettingSignalMask(e) => write!(f, "failed to set the signal mask for vcpu: {}", e),
|
||||||
SettingUidMap(e) => write!(f, "error setting UID map: {}", e),
|
SettingUidMap(e) => write!(f, "error setting UID map: {}", e),
|
||||||
SignalFd(e) => write!(f, "failed to read signal fd: {}", e),
|
SignalFd(e) => write!(f, "failed to read signal fd: {}", e),
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
SpawnGdbServer(e) => write!(f, "failed to spawn GDB thread: {}", e),
|
||||||
SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
|
SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
|
||||||
Timer(e) => write!(f, "failed to read timer fd: {}", e),
|
Timer(e) => write!(f, "failed to read timer fd: {}", e),
|
||||||
ValidateRawFd(e) => write!(f, "failed to validate raw fd: {}", e),
|
ValidateRawFd(e) => write!(f, "failed to validate raw fd: {}", e),
|
||||||
|
@ -300,10 +316,6 @@ enum TaggedControlSocket {
|
||||||
VmMsync(VmMsyncResponseSocket),
|
VmMsync(VmMsyncResponseSocket),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VcpuControl {
|
|
||||||
RunState(VmRunMode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<UnixSeqpacket> for TaggedControlSocket {
|
impl AsRef<UnixSeqpacket> for TaggedControlSocket {
|
||||||
fn as_ref(&self) -> &UnixSeqpacket {
|
fn as_ref(&self) -> &UnixSeqpacket {
|
||||||
use self::TaggedControlSocket::*;
|
use self::TaggedControlSocket::*;
|
||||||
|
@ -1700,6 +1712,63 @@ fn inject_interrupt(irq_chip: &mut dyn IrqChipX86_64, vcpu: &dyn VcpuX86_64, vcp
|
||||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||||
fn inject_interrupt(_irq_chip: &mut dyn IrqChip, _vcpu: &dyn Vcpu, _vcpu_id: usize) {}
|
fn inject_interrupt(_irq_chip: &mut dyn IrqChip, _vcpu: &dyn Vcpu, _vcpu_id: usize) {}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
fn handle_debug_msg<V>(
|
||||||
|
cpu_id: usize,
|
||||||
|
vcpu: &V,
|
||||||
|
guest_mem: &GuestMemory,
|
||||||
|
d: VcpuDebug,
|
||||||
|
reply_channel: &mpsc::Sender<VcpuDebugStatusMessage>,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
V: VcpuArch + 'static,
|
||||||
|
{
|
||||||
|
match d {
|
||||||
|
VcpuDebug::ReadRegs => {
|
||||||
|
let msg = VcpuDebugStatusMessage {
|
||||||
|
cpu: cpu_id as usize,
|
||||||
|
msg: VcpuDebugStatus::RegValues(
|
||||||
|
Arch::debug_read_registers(vcpu as &V).map_err(Error::HandleDebugCommand)?,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
reply_channel
|
||||||
|
.send(msg)
|
||||||
|
.map_err(|e| Error::SendDebugStatus(Box::new(e)))
|
||||||
|
}
|
||||||
|
VcpuDebug::WriteRegs(regs) => {
|
||||||
|
Arch::debug_write_registers(vcpu as &V, ®s).map_err(Error::HandleDebugCommand)?;
|
||||||
|
reply_channel
|
||||||
|
.send(VcpuDebugStatusMessage {
|
||||||
|
cpu: cpu_id as usize,
|
||||||
|
msg: VcpuDebugStatus::CommandComplete,
|
||||||
|
})
|
||||||
|
.map_err(|e| Error::SendDebugStatus(Box::new(e)))
|
||||||
|
}
|
||||||
|
VcpuDebug::ReadMem(vaddr, len) => {
|
||||||
|
let msg = VcpuDebugStatusMessage {
|
||||||
|
cpu: cpu_id as usize,
|
||||||
|
msg: VcpuDebugStatus::MemoryRegion(
|
||||||
|
Arch::debug_read_memory(vcpu as &V, guest_mem, vaddr, len)
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
reply_channel
|
||||||
|
.send(msg)
|
||||||
|
.map_err(|e| Error::SendDebugStatus(Box::new(e)))
|
||||||
|
}
|
||||||
|
VcpuDebug::WriteMem(vaddr, buf) => {
|
||||||
|
Arch::debug_write_memory(vcpu as &V, guest_mem, vaddr, &buf)
|
||||||
|
.map_err(Error::HandleDebugCommand)?;
|
||||||
|
reply_channel
|
||||||
|
.send(VcpuDebugStatusMessage {
|
||||||
|
cpu: cpu_id as usize,
|
||||||
|
msg: VcpuDebugStatus::CommandComplete,
|
||||||
|
})
|
||||||
|
.map_err(|e| Error::SendDebugStatus(Box::new(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn run_vcpu<V>(
|
fn run_vcpu<V>(
|
||||||
cpu_id: usize,
|
cpu_id: usize,
|
||||||
vcpu: Option<V>,
|
vcpu: Option<V>,
|
||||||
|
@ -1717,6 +1786,9 @@ fn run_vcpu<V>(
|
||||||
requires_pvclock_ctrl: bool,
|
requires_pvclock_ctrl: bool,
|
||||||
from_main_channel: mpsc::Receiver<VcpuControl>,
|
from_main_channel: mpsc::Receiver<VcpuControl>,
|
||||||
use_hypervisor_signals: bool,
|
use_hypervisor_signals: bool,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))] to_gdb_channel: Option<
|
||||||
|
mpsc::Sender<VcpuDebugStatusMessage>,
|
||||||
|
>,
|
||||||
) -> Result<JoinHandle<()>>
|
) -> Result<JoinHandle<()>>
|
||||||
where
|
where
|
||||||
V: VcpuArch + 'static,
|
V: VcpuArch + 'static,
|
||||||
|
@ -1728,6 +1800,8 @@ where
|
||||||
// implementation accomplishes that.
|
// implementation accomplishes that.
|
||||||
let _scoped_exit_evt = ScopedEvent::from(exit_evt);
|
let _scoped_exit_evt = ScopedEvent::from(exit_evt);
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
let guest_mem = vm.get_memory().clone();
|
||||||
let runnable_vcpu = runnable_vcpu(
|
let runnable_vcpu = runnable_vcpu(
|
||||||
cpu_id,
|
cpu_id,
|
||||||
vcpu,
|
vcpu,
|
||||||
|
@ -1752,6 +1826,12 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut run_mode = VmRunMode::Running;
|
let mut run_mode = VmRunMode::Running;
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
if to_gdb_channel.is_some() {
|
||||||
|
// Wait until a GDB client attaches
|
||||||
|
run_mode = VmRunMode::Breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
let mut interrupted_by_signal = false;
|
let mut interrupted_by_signal = false;
|
||||||
|
|
||||||
'vcpu_loop: loop {
|
'vcpu_loop: loop {
|
||||||
|
@ -1765,7 +1845,7 @@ where
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(mpsc::TryRecvError::Empty) if run_mode == VmRunMode::Running => {
|
Err(mpsc::TryRecvError::Empty) if run_mode == VmRunMode::Running => {
|
||||||
// If the VM is running and no message is pending, the state won't
|
// If the VM is running and no message is pending, the state won't
|
||||||
// be changed.
|
// change.
|
||||||
break 'state_loop;
|
break 'state_loop;
|
||||||
}
|
}
|
||||||
Err(mpsc::TryRecvError::Empty) => {
|
Err(mpsc::TryRecvError::Empty) => {
|
||||||
|
@ -1788,26 +1868,47 @@ where
|
||||||
let mut messages = vec![msg];
|
let mut messages = vec![msg];
|
||||||
messages.append(&mut from_main_channel.try_iter().collect());
|
messages.append(&mut from_main_channel.try_iter().collect());
|
||||||
|
|
||||||
for VcpuControl::RunState(new_mode) in messages {
|
for msg in messages {
|
||||||
run_mode = new_mode.clone();
|
match msg {
|
||||||
match run_mode {
|
VcpuControl::RunState(new_mode) => {
|
||||||
VmRunMode::Running => break 'state_loop,
|
run_mode = new_mode;
|
||||||
VmRunMode::Suspending => {
|
match run_mode {
|
||||||
// On KVM implementations that use a paravirtualized clock (e.g.
|
VmRunMode::Running => break 'state_loop,
|
||||||
// x86), a flag must be set to indicate to the guest kernel that a
|
VmRunMode::Suspending => {
|
||||||
// VCPU was suspended. The guest kernel will use this flag to
|
// On KVM implementations that use a paravirtualized
|
||||||
// prevent the soft lockup detection from triggering when this VCPU
|
// clock (e.g. x86), a flag must be set to indicate to
|
||||||
// resumes, which could happen days later in realtime.
|
// the guest kernel that a vCPU was suspended. The guest
|
||||||
if requires_pvclock_ctrl {
|
// kernel will use this flag to prevent the soft lockup
|
||||||
if let Err(e) = vcpu.pvclock_ctrl() {
|
// detection from triggering when this vCPU resumes,
|
||||||
error!(
|
// which could happen days later in realtime.
|
||||||
"failed to tell hypervisor vcpu {} is suspending: {}",
|
if requires_pvclock_ctrl {
|
||||||
cpu_id, e
|
if let Err(e) = vcpu.pvclock_ctrl() {
|
||||||
);
|
error!(
|
||||||
|
"failed to tell hypervisor vcpu {} is suspending: {}",
|
||||||
|
cpu_id, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VmRunMode::Breakpoint => {}
|
||||||
|
VmRunMode::Exiting => break 'vcpu_loop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
VcpuControl::Debug(d) => {
|
||||||
|
match &to_gdb_channel {
|
||||||
|
Some(ref ch) => {
|
||||||
|
if let Err(e) = handle_debug_msg(
|
||||||
|
cpu_id, &vcpu, &guest_mem, d, &ch,
|
||||||
|
) {
|
||||||
|
error!("Failed to handle gdb message: {}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
error!("VcpuControl::Debug received while GDB feature is disabled: {:?}", d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VmRunMode::Exiting => break 'vcpu_loop,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2010,6 +2111,18 @@ where
|
||||||
_ => panic!("Did not receive a bios or kernel, should be impossible."),
|
_ => panic!("Did not receive a bios or kernel, should be impossible."),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut control_sockets = Vec::new();
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
let gdb_socket = if let Some(port) = cfg.gdb {
|
||||||
|
// GDB needs a control socket to interrupt vcpus.
|
||||||
|
let (gdb_host_socket, gdb_control_socket) =
|
||||||
|
msg_socket::pair::<VmResponse, VmRequest>().map_err(Error::CreateSocket)?;
|
||||||
|
control_sockets.push(TaggedControlSocket::Vm(gdb_host_socket));
|
||||||
|
Some((port, gdb_control_socket))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let components = VmComponents {
|
let components = VmComponents {
|
||||||
memory_size: cfg
|
memory_size: cfg
|
||||||
.memory
|
.memory
|
||||||
|
@ -2036,6 +2149,8 @@ where
|
||||||
.collect::<Result<Vec<SDT>>>()?,
|
.collect::<Result<Vec<SDT>>>()?,
|
||||||
rt_cpus: cfg.rt_cpus.clone(),
|
rt_cpus: cfg.rt_cpus.clone(),
|
||||||
protected_vm: cfg.protected_vm,
|
protected_vm: cfg.protected_vm,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
gdb: gdb_socket,
|
||||||
};
|
};
|
||||||
|
|
||||||
let control_server_socket = match &cfg.socket_path {
|
let control_server_socket = match &cfg.socket_path {
|
||||||
|
@ -2045,7 +2160,6 @@ where
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut control_sockets = Vec::new();
|
|
||||||
let (wayland_host_socket, wayland_device_socket) =
|
let (wayland_host_socket, wayland_device_socket) =
|
||||||
msg_socket::pair::<VmMemoryResponse, VmMemoryRequest>().map_err(Error::CreateSocket)?;
|
msg_socket::pair::<VmMemoryResponse, VmMemoryRequest>().map_err(Error::CreateSocket)?;
|
||||||
control_sockets.push(TaggedControlSocket::VmMemory(wayland_host_socket));
|
control_sockets.push(TaggedControlSocket::VmMemory(wayland_host_socket));
|
||||||
|
@ -2209,6 +2323,15 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static, I: IrqChipArch + '
|
||||||
drop_capabilities().map_err(Error::DropCapabilities)?;
|
drop_capabilities().map_err(Error::DropCapabilities)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
// Create a channel for GDB thread.
|
||||||
|
let (to_gdb_channel, from_vcpu_channel) = if linux.gdb.is_some() {
|
||||||
|
let (s, r) = mpsc::channel();
|
||||||
|
(Some(s), Some(r))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
let mut vcpu_handles = Vec::with_capacity(linux.vcpu_count);
|
let mut vcpu_handles = Vec::with_capacity(linux.vcpu_count);
|
||||||
let vcpu_thread_barrier = Arc::new(Barrier::new(linux.vcpu_count + 1));
|
let vcpu_thread_barrier = Arc::new(Barrier::new(linux.vcpu_count + 1));
|
||||||
let use_hypervisor_signals = !linux
|
let use_hypervisor_signals = !linux
|
||||||
|
@ -2245,10 +2368,30 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static, I: IrqChipArch + '
|
||||||
linux.vm.check_capability(VmCap::PvClockSuspend),
|
linux.vm.check_capability(VmCap::PvClockSuspend),
|
||||||
from_main_channel,
|
from_main_channel,
|
||||||
use_hypervisor_signals,
|
use_hypervisor_signals,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
to_gdb_channel.clone(),
|
||||||
)?;
|
)?;
|
||||||
vcpu_handles.push((handle, to_vcpu_channel));
|
vcpu_handles.push((handle, to_vcpu_channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
// Spawn GDB thread.
|
||||||
|
if let Some((gdb_port_num, gdb_control_socket)) = linux.gdb.take() {
|
||||||
|
let to_vcpu_channels = vcpu_handles
|
||||||
|
.iter()
|
||||||
|
.map(|(_handle, channel)| channel.clone())
|
||||||
|
.collect();
|
||||||
|
let target = GdbStub::new(
|
||||||
|
gdb_control_socket,
|
||||||
|
to_vcpu_channels,
|
||||||
|
from_vcpu_channel.unwrap(), // Must succeed to unwrap()
|
||||||
|
);
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("gdb".to_owned())
|
||||||
|
.spawn(move || gdb_thread(target, gdb_port_num))
|
||||||
|
.map_err(Error::SpawnGdbServer)?;
|
||||||
|
};
|
||||||
|
|
||||||
vcpu_thread_barrier.wait();
|
vcpu_thread_barrier.wait();
|
||||||
|
|
||||||
'wait: loop {
|
'wait: loop {
|
||||||
|
@ -2426,14 +2569,14 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static, I: IrqChipArch + '
|
||||||
VmRunMode::Exiting => {
|
VmRunMode::Exiting => {
|
||||||
break 'wait;
|
break 'wait;
|
||||||
}
|
}
|
||||||
VmRunMode::Suspending | VmRunMode::Running => {
|
other => {
|
||||||
if run_mode == VmRunMode::Suspending {
|
if other == VmRunMode::Suspending {
|
||||||
linux.io_bus.notify_resume();
|
linux.io_bus.notify_resume();
|
||||||
}
|
}
|
||||||
for (handle, channel) in &vcpu_handles {
|
for (handle, channel) in &vcpu_handles {
|
||||||
if let Err(e) = channel.send(
|
if let Err(e) = channel
|
||||||
VcpuControl::RunState(VmRunMode::Running),
|
.send(VcpuControl::RunState(other.clone()))
|
||||||
) {
|
{
|
||||||
error!("failed to send VmRunMode: {}", e);
|
error!("failed to send VmRunMode: {}", e);
|
||||||
}
|
}
|
||||||
let _ = handle.kill(SIGRTMIN() + 0);
|
let _ = handle.kill(SIGRTMIN() + 0);
|
||||||
|
|
21
src/main.rs
21
src/main.rs
|
@ -1438,7 +1438,17 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
|
||||||
let params = parse_battery_options(value)?;
|
let params = parse_battery_options(value)?;
|
||||||
cfg.battery_type = Some(params);
|
cfg.battery_type = Some(params);
|
||||||
}
|
}
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
"gdb" => {
|
||||||
|
let port = value
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| argument::Error::InvalidValue {
|
||||||
|
value: value.unwrap().to_owned(),
|
||||||
|
expected: String::from("expected a valid port number"),
|
||||||
|
})?;
|
||||||
|
cfg.gdb = Some(port);
|
||||||
|
}
|
||||||
"help" => return Err(argument::Error::PrintHelp),
|
"help" => return Err(argument::Error::PrintHelp),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -1480,6 +1490,14 @@ fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
if cfg.gdb.is_some() {
|
||||||
|
if cfg.vcpu_count.unwrap_or(1) != 1 {
|
||||||
|
return Err(argument::Error::ExpectedArgument(
|
||||||
|
"`gdb` requires the number of vCPU to be 1".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
set_default_serial_parameters(&mut cfg.serial_parameters);
|
set_default_serial_parameters(&mut cfg.serial_parameters);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1627,6 +1645,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
|
||||||
Possible key values:
|
Possible key values:
|
||||||
type=goldfish - type of battery emulation, defaults to goldfish
|
type=goldfish - type of battery emulation, defaults to goldfish
|
||||||
"),
|
"),
|
||||||
|
Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"),
|
||||||
Argument::short_flag('h', "help", "Print help message.")];
|
Argument::short_flag('h', "help", "Print help message.")];
|
||||||
|
|
||||||
let mut cfg = Config::default();
|
let mut cfg = Config::default();
|
||||||
|
|
|
@ -4,8 +4,12 @@ version = "0.1.0"
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
gdb = ["gdbstub"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
data_model = { path = "../data_model" }
|
data_model = { path = "../data_model" }
|
||||||
|
gdbstub = { version = "0.4.0", optional = true }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
libc = "*"
|
libc = "*"
|
||||||
msg_socket = { path = "../msg_socket" }
|
msg_socket = { path = "../msg_socket" }
|
||||||
|
|
32
vm_control/src/gdb.rs
Normal file
32
vm_control/src/gdb.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2020 The Chromium OS Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
use gdbstub::arch::x86::reg::X86_64CoreRegs as CoreRegs;
|
||||||
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
|
/// Messages that can be sent to a vCPU to set/get its state from the debugger.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VcpuDebug {
|
||||||
|
ReadMem(GuestAddress, usize),
|
||||||
|
ReadRegs,
|
||||||
|
WriteRegs(Box<CoreRegs>),
|
||||||
|
WriteMem(GuestAddress, Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Messages that can be sent from a vCPU to update the state to the debugger.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VcpuDebugStatus {
|
||||||
|
RegValues(CoreRegs),
|
||||||
|
MemoryRegion(Vec<u8>),
|
||||||
|
CommandComplete,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pair of a vCPU ID and messages that can be sent from the vCPU to update the state to the
|
||||||
|
/// debugger.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VcpuDebugStatusMessage {
|
||||||
|
pub cpu: usize,
|
||||||
|
pub msg: VcpuDebugStatus,
|
||||||
|
}
|
|
@ -10,6 +10,9 @@
|
||||||
//! The wire message format is a little-endian C-struct of fixed size, along with a file descriptor
|
//! The wire message format is a little-endian C-struct of fixed size, along with a file descriptor
|
||||||
//! if the request type expects one.
|
//! if the request type expects one.
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
pub mod gdb;
|
||||||
|
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Seek, SeekFrom};
|
use std::io::{Seek, SeekFrom};
|
||||||
|
@ -31,8 +34,18 @@ use resources::{Alloc, GpuMemoryDesc, MmioType, SystemAllocator};
|
||||||
use sync::Mutex;
|
use sync::Mutex;
|
||||||
use vm_memory::GuestAddress;
|
use vm_memory::GuestAddress;
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
pub use crate::gdb::*;
|
||||||
pub use hypervisor::MemSlot;
|
pub use hypervisor::MemSlot;
|
||||||
|
|
||||||
|
/// Control the state of a particular VM CPU.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VcpuControl {
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
Debug(VcpuDebug),
|
||||||
|
RunState(VmRunMode),
|
||||||
|
}
|
||||||
|
|
||||||
/// A file descriptor either borrowed or owned by this.
|
/// A file descriptor either borrowed or owned by this.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MaybeOwnedDescriptor {
|
pub enum MaybeOwnedDescriptor {
|
||||||
|
@ -102,6 +115,8 @@ pub enum VmRunMode {
|
||||||
Suspending,
|
Suspending,
|
||||||
/// Indicates that the VM is exiting all processes.
|
/// Indicates that the VM is exiting all processes.
|
||||||
Exiting,
|
Exiting,
|
||||||
|
/// Indicates that the VM is in a breakpoint waiting for the debugger to do continue.
|
||||||
|
Breakpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VmRunMode {
|
impl Display for VmRunMode {
|
||||||
|
@ -112,6 +127,7 @@ impl Display for VmRunMode {
|
||||||
Running => write!(f, "running"),
|
Running => write!(f, "running"),
|
||||||
Suspending => write!(f, "suspending"),
|
Suspending => write!(f, "suspending"),
|
||||||
Exiting => write!(f, "exiting"),
|
Exiting => write!(f, "exiting"),
|
||||||
|
Breakpoint => write!(f, "breakpoint"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,20 @@ version = "0.1.0"
|
||||||
authors = ["The Chromium OS Authors"]
|
authors = ["The Chromium OS Authors"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
gdb = ["gdbstub", "msg_socket", "arch/gdb"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arch = { path = "../arch" }
|
arch = { path = "../arch" }
|
||||||
assertions = { path = "../assertions" }
|
assertions = { path = "../assertions" }
|
||||||
data_model = { path = "../data_model" }
|
data_model = { path = "../data_model" }
|
||||||
devices = { path = "../devices" }
|
devices = { path = "../devices" }
|
||||||
|
gdbstub = { version = "0.4.0", optional = true }
|
||||||
hypervisor = { path = "../hypervisor" }
|
hypervisor = { path = "../hypervisor" }
|
||||||
kernel_cmdline = { path = "../kernel_cmdline" }
|
kernel_cmdline = { path = "../kernel_cmdline" }
|
||||||
kernel_loader = { path = "../kernel_loader" }
|
kernel_loader = { path = "../kernel_loader" }
|
||||||
libc = "*"
|
libc = "*"
|
||||||
|
msg_socket = { path = "../msg_socket", optional = true }
|
||||||
minijail = "*"
|
minijail = "*"
|
||||||
remain = "*"
|
remain = "*"
|
||||||
resources = { path = "../resources" }
|
resources = { path = "../resources" }
|
||||||
|
|
|
@ -69,6 +69,11 @@ use resources::SystemAllocator;
|
||||||
use sync::Mutex;
|
use sync::Mutex;
|
||||||
use vm_control::BatteryType;
|
use vm_control::BatteryType;
|
||||||
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
|
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
use {
|
||||||
|
gdbstub::arch::x86::reg::X86_64CoreRegs,
|
||||||
|
hypervisor::x86_64::{Regs, Sregs},
|
||||||
|
};
|
||||||
|
|
||||||
#[sorted]
|
#[sorted]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -100,7 +105,10 @@ pub enum Error {
|
||||||
LoadCmdline(kernel_loader::Error),
|
LoadCmdline(kernel_loader::Error),
|
||||||
LoadInitrd(arch::LoadImageError),
|
LoadInitrd(arch::LoadImageError),
|
||||||
LoadKernel(kernel_loader::Error),
|
LoadKernel(kernel_loader::Error),
|
||||||
|
PageNotPresent,
|
||||||
Pstore(arch::pstore::Error),
|
Pstore(arch::pstore::Error),
|
||||||
|
ReadingGuestMemory(vm_memory::GuestMemoryError),
|
||||||
|
ReadRegs(base::Error),
|
||||||
RegisterIrqfd(base::Error),
|
RegisterIrqfd(base::Error),
|
||||||
RegisterVsock(arch::DeviceRegistrationError),
|
RegisterVsock(arch::DeviceRegistrationError),
|
||||||
SetLint(interrupts::Error),
|
SetLint(interrupts::Error),
|
||||||
|
@ -113,6 +121,9 @@ pub enum Error {
|
||||||
SetupRegs(regs::Error),
|
SetupRegs(regs::Error),
|
||||||
SetupSmbios(smbios::Error),
|
SetupSmbios(smbios::Error),
|
||||||
SetupSregs(regs::Error),
|
SetupSregs(regs::Error),
|
||||||
|
TranslatingVirtAddr,
|
||||||
|
WriteRegs(base::Error),
|
||||||
|
WritingGuestMemory(GuestMemoryError),
|
||||||
ZeroPagePastRamEnd,
|
ZeroPagePastRamEnd,
|
||||||
ZeroPageSetup,
|
ZeroPageSetup,
|
||||||
}
|
}
|
||||||
|
@ -151,7 +162,10 @@ impl Display for Error {
|
||||||
LoadCmdline(e) => write!(f, "error loading command line: {}", e),
|
LoadCmdline(e) => write!(f, "error loading command line: {}", e),
|
||||||
LoadInitrd(e) => write!(f, "error loading initrd: {}", e),
|
LoadInitrd(e) => write!(f, "error loading initrd: {}", e),
|
||||||
LoadKernel(e) => write!(f, "error loading Kernel: {}", e),
|
LoadKernel(e) => write!(f, "error loading Kernel: {}", e),
|
||||||
|
PageNotPresent => write!(f, "error translating address: Page not present"),
|
||||||
Pstore(e) => write!(f, "failed to allocate pstore region: {}", e),
|
Pstore(e) => write!(f, "failed to allocate pstore region: {}", e),
|
||||||
|
ReadingGuestMemory(e) => write!(f, "error reading guest memory {}", e),
|
||||||
|
ReadRegs(e) => write!(f, "error reading CPU registers {}", e),
|
||||||
RegisterIrqfd(e) => write!(f, "error registering an IrqFd: {}", e),
|
RegisterIrqfd(e) => write!(f, "error registering an IrqFd: {}", e),
|
||||||
RegisterVsock(e) => write!(f, "error registering virtual socket device: {}", e),
|
RegisterVsock(e) => write!(f, "error registering virtual socket device: {}", e),
|
||||||
SetLint(e) => write!(f, "failed to set interrupts: {}", e),
|
SetLint(e) => write!(f, "failed to set interrupts: {}", e),
|
||||||
|
@ -164,6 +178,9 @@ impl Display for Error {
|
||||||
SetupRegs(e) => write!(f, "failed to set up registers: {}", e),
|
SetupRegs(e) => write!(f, "failed to set up registers: {}", e),
|
||||||
SetupSmbios(e) => write!(f, "failed to set up SMBIOS: {}", e),
|
SetupSmbios(e) => write!(f, "failed to set up SMBIOS: {}", e),
|
||||||
SetupSregs(e) => write!(f, "failed to set up sregs: {}", e),
|
SetupSregs(e) => write!(f, "failed to set up sregs: {}", e),
|
||||||
|
TranslatingVirtAddr => write!(f, "failed to translate virtual address"),
|
||||||
|
WriteRegs(e) => write!(f, "error writing CPU registers {}", e),
|
||||||
|
WritingGuestMemory(e) => write!(f, "error writing guest memory {}", e),
|
||||||
ZeroPagePastRamEnd => write!(f, "the zero page extends past the end of guest_mem"),
|
ZeroPagePastRamEnd => write!(f, "the zero page extends past the end of guest_mem"),
|
||||||
ZeroPageSetup => write!(f, "error writing the zero page of guest memory"),
|
ZeroPageSetup => write!(f, "error writing the zero page of guest memory"),
|
||||||
}
|
}
|
||||||
|
@ -493,6 +510,8 @@ impl arch::LinuxArch for X8664arch {
|
||||||
pid_debug_label_map,
|
pid_debug_label_map,
|
||||||
suspend_evt,
|
suspend_evt,
|
||||||
rt_cpus: components.rt_cpus,
|
rt_cpus: components.rt_cpus,
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
gdb: components.gdb,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,6 +550,219 @@ impl arch::LinuxArch for X8664arch {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
fn debug_read_registers<T: VcpuX86_64>(vcpu: &T) -> Result<X86_64CoreRegs> {
|
||||||
|
// General registers: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, r8-r15
|
||||||
|
let gregs = vcpu.get_regs().map_err(Error::ReadRegs)?;
|
||||||
|
let regs = [
|
||||||
|
gregs.rax, gregs.rbx, gregs.rcx, gregs.rdx, gregs.rsi, gregs.rdi, gregs.rbp, gregs.rsp,
|
||||||
|
gregs.r8, gregs.r9, gregs.r10, gregs.r11, gregs.r12, gregs.r13, gregs.r14, gregs.r15,
|
||||||
|
];
|
||||||
|
|
||||||
|
// GDB exposes 32-bit eflags instead of 64-bit rflags.
|
||||||
|
// https://github.com/bminor/binutils-gdb/blob/master/gdb/features/i386/64bit-core.xml
|
||||||
|
let eflags = gregs.rflags as u32;
|
||||||
|
let rip = gregs.rip;
|
||||||
|
|
||||||
|
// Segment registers: CS, SS, DS, ES, FS, GS
|
||||||
|
let sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
|
||||||
|
let sgs = [sregs.cs, sregs.ss, sregs.ds, sregs.es, sregs.fs, sregs.gs];
|
||||||
|
let mut segments = [0u32; 6];
|
||||||
|
// GDB uses only the selectors.
|
||||||
|
for i in 0..sgs.len() {
|
||||||
|
segments[i] = sgs[i].selector as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(keiichiw): Other registers such as FPU, xmm and mxcsr.
|
||||||
|
|
||||||
|
Ok(X86_64CoreRegs {
|
||||||
|
regs,
|
||||||
|
eflags,
|
||||||
|
rip,
|
||||||
|
segments,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
fn debug_write_registers<T: VcpuX86_64>(vcpu: &T, regs: &X86_64CoreRegs) -> Result<()> {
|
||||||
|
// General purpose registers (RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, r8-r15) + RIP + rflags
|
||||||
|
let orig_gregs = vcpu.get_regs().map_err(Error::ReadRegs)?;
|
||||||
|
let gregs = Regs {
|
||||||
|
rax: regs.regs[0],
|
||||||
|
rbx: regs.regs[1],
|
||||||
|
rcx: regs.regs[2],
|
||||||
|
rdx: regs.regs[3],
|
||||||
|
rsi: regs.regs[4],
|
||||||
|
rdi: regs.regs[5],
|
||||||
|
rbp: regs.regs[6],
|
||||||
|
rsp: regs.regs[7],
|
||||||
|
r8: regs.regs[8],
|
||||||
|
r9: regs.regs[9],
|
||||||
|
r10: regs.regs[10],
|
||||||
|
r11: regs.regs[11],
|
||||||
|
r12: regs.regs[12],
|
||||||
|
r13: regs.regs[13],
|
||||||
|
r14: regs.regs[14],
|
||||||
|
r15: regs.regs[15],
|
||||||
|
rip: regs.rip,
|
||||||
|
// Update the lower 32 bits of rflags.
|
||||||
|
rflags: (orig_gregs.rflags & !(u32::MAX as u64)) | (regs.eflags as u64),
|
||||||
|
};
|
||||||
|
vcpu.set_regs(&gregs).map_err(Error::WriteRegs)?;
|
||||||
|
|
||||||
|
// Segment registers: CS, SS, DS, ES, FS, GS
|
||||||
|
// Since GDB care only selectors, we call get_sregs() first.
|
||||||
|
let mut sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
|
||||||
|
sregs.cs.selector = regs.segments[0] as u16;
|
||||||
|
sregs.ss.selector = regs.segments[1] as u16;
|
||||||
|
sregs.ds.selector = regs.segments[2] as u16;
|
||||||
|
sregs.es.selector = regs.segments[3] as u16;
|
||||||
|
sregs.fs.selector = regs.segments[4] as u16;
|
||||||
|
sregs.gs.selector = regs.segments[5] as u16;
|
||||||
|
|
||||||
|
vcpu.set_sregs(&sregs).map_err(Error::WriteRegs)?;
|
||||||
|
|
||||||
|
// TODO(keiichiw): Other registers such as FPU, xmm and mxcsr.
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
fn debug_read_memory<T: VcpuX86_64>(
|
||||||
|
vcpu: &T,
|
||||||
|
guest_mem: &GuestMemory,
|
||||||
|
vaddr: GuestAddress,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
let sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
|
||||||
|
let mut buf = vec![0; len];
|
||||||
|
let mut total_read = 0u64;
|
||||||
|
// Handle reads across page boundaries.
|
||||||
|
|
||||||
|
while total_read < len as u64 {
|
||||||
|
let (paddr, psize) = phys_addr(guest_mem, vaddr.0 + total_read, &sregs)?;
|
||||||
|
let read_len = std::cmp::min(len as u64 - total_read, psize - (paddr & (psize - 1)));
|
||||||
|
guest_mem
|
||||||
|
.get_slice_at_addr(GuestAddress(paddr), read_len as usize)
|
||||||
|
.map_err(Error::ReadingGuestMemory)?
|
||||||
|
.copy_to(&mut buf[total_read as usize..]);
|
||||||
|
total_read += read_len;
|
||||||
|
}
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
fn debug_write_memory<T: VcpuX86_64>(
|
||||||
|
vcpu: &T,
|
||||||
|
guest_mem: &GuestMemory,
|
||||||
|
vaddr: GuestAddress,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
|
||||||
|
let mut total_written = 0u64;
|
||||||
|
// Handle writes across page boundaries.
|
||||||
|
while total_written < buf.len() as u64 {
|
||||||
|
let (paddr, psize) = phys_addr(guest_mem, vaddr.0 + total_written, &sregs)?;
|
||||||
|
let write_len = std::cmp::min(
|
||||||
|
buf.len() as u64 - total_written,
|
||||||
|
psize - (paddr & (psize - 1)),
|
||||||
|
);
|
||||||
|
|
||||||
|
guest_mem
|
||||||
|
.write_all_at_addr(
|
||||||
|
&buf[total_written as usize..(total_written as usize + write_len as usize)],
|
||||||
|
GuestAddress(paddr),
|
||||||
|
)
|
||||||
|
.map_err(Error::WritingGuestMemory)?;
|
||||||
|
total_written += write_len;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||||
|
// return the translated address and the size of the page it resides in.
|
||||||
|
fn phys_addr(mem: &GuestMemory, vaddr: u64, sregs: &Sregs) -> Result<(u64, u64)> {
|
||||||
|
const CR0_PG_MASK: u64 = 1 << 31;
|
||||||
|
const CR4_PAE_MASK: u64 = 1 << 5;
|
||||||
|
const CR4_LA57_MASK: u64 = 1 << 12;
|
||||||
|
const MSR_EFER_LMA: u64 = 1 << 10;
|
||||||
|
// bits 12 through 51 are the address in a PTE.
|
||||||
|
const PTE_ADDR_MASK: u64 = ((1 << 52) - 1) & !0x0fff;
|
||||||
|
const PAGE_PRESENT: u64 = 0x1;
|
||||||
|
const PAGE_PSE_MASK: u64 = 0x1 << 7;
|
||||||
|
|
||||||
|
const PAGE_SIZE_4K: u64 = 4 * 1024;
|
||||||
|
const PAGE_SIZE_2M: u64 = 2 * 1024 * 1024;
|
||||||
|
const PAGE_SIZE_1G: u64 = 1024 * 1024 * 1024;
|
||||||
|
|
||||||
|
fn next_pte(mem: &GuestMemory, curr_table_addr: u64, vaddr: u64, level: usize) -> Result<u64> {
|
||||||
|
let ent: u64 = mem
|
||||||
|
.read_obj_from_addr(GuestAddress(
|
||||||
|
(curr_table_addr & PTE_ADDR_MASK) + page_table_offset(vaddr, level),
|
||||||
|
))
|
||||||
|
.map_err(|_| Error::TranslatingVirtAddr)?;
|
||||||
|
/* TODO - convert to a trace
|
||||||
|
println!(
|
||||||
|
"level {} vaddr {:x} table-addr {:x} mask {:x} ent {:x} offset {:x}",
|
||||||
|
level,
|
||||||
|
vaddr,
|
||||||
|
curr_table_addr,
|
||||||
|
PTE_ADDR_MASK,
|
||||||
|
ent,
|
||||||
|
page_table_offset(vaddr, level)
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
if ent & PAGE_PRESENT == 0 {
|
||||||
|
return Err(Error::PageNotPresent);
|
||||||
|
}
|
||||||
|
Ok(ent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the offset in to the page of `vaddr`.
|
||||||
|
fn page_offset(vaddr: u64, page_size: u64) -> u64 {
|
||||||
|
vaddr & (page_size - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the offset in to the page table of the given `level` specified by the virtual `address`.
|
||||||
|
// `level` is 1 through 5 in x86_64 to handle the five levels of paging.
|
||||||
|
fn page_table_offset(addr: u64, level: usize) -> u64 {
|
||||||
|
let offset = (level - 1) * 9 + 12;
|
||||||
|
((addr >> offset) & 0x1ff) << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if sregs.cr0 & CR0_PG_MASK == 0 {
|
||||||
|
return Ok((vaddr, PAGE_SIZE_4K));
|
||||||
|
}
|
||||||
|
|
||||||
|
if sregs.cr4 & CR4_PAE_MASK == 0 {
|
||||||
|
return Err(Error::TranslatingVirtAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if sregs.efer & MSR_EFER_LMA != 0 {
|
||||||
|
// TODO - check LA57
|
||||||
|
if sregs.cr4 & CR4_LA57_MASK != 0 {}
|
||||||
|
let p4_ent = next_pte(mem, sregs.cr3, vaddr, 4)?;
|
||||||
|
let p3_ent = next_pte(mem, p4_ent, vaddr, 3)?;
|
||||||
|
// TODO check if it's a 1G page with the PSE bit in p2_ent
|
||||||
|
if p3_ent & PAGE_PSE_MASK != 0 {
|
||||||
|
// It's a 1G page with the PSE bit in p3_ent
|
||||||
|
let paddr = p3_ent & PTE_ADDR_MASK | page_offset(vaddr, PAGE_SIZE_1G);
|
||||||
|
return Ok((paddr, PAGE_SIZE_1G));
|
||||||
|
}
|
||||||
|
let p2_ent = next_pte(mem, p3_ent, vaddr, 2)?;
|
||||||
|
if p2_ent & PAGE_PSE_MASK != 0 {
|
||||||
|
// It's a 2M page with the PSE bit in p2_ent
|
||||||
|
let paddr = p2_ent & PTE_ADDR_MASK | page_offset(vaddr, PAGE_SIZE_2M);
|
||||||
|
return Ok((paddr, PAGE_SIZE_2M));
|
||||||
|
}
|
||||||
|
let p1_ent = next_pte(mem, p2_ent, vaddr, 1)?;
|
||||||
|
let paddr = p1_ent & PTE_ADDR_MASK | page_offset(vaddr, PAGE_SIZE_4K);
|
||||||
|
return Ok((paddr, PAGE_SIZE_4K));
|
||||||
|
}
|
||||||
|
Err(Error::TranslatingVirtAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl X8664arch {
|
impl X8664arch {
|
||||||
|
|
Loading…
Reference in a new issue