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",
|
||||
"base 0.1.0",
|
||||
"devices 0.1.0",
|
||||
"gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hypervisor 0.1.0",
|
||||
"kernel_cmdline 0.1.0",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"minijail 0.2.1",
|
||||
"msg_socket 0.1.0",
|
||||
"resources 0.1.0",
|
||||
"sync 0.1.0",
|
||||
"vm_control 0.1.0",
|
||||
|
@ -160,6 +162,7 @@ dependencies = [
|
|||
"devices 0.1.0",
|
||||
"disk 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_renderer 0.1.0",
|
||||
"hypervisor 0.1.0",
|
||||
|
@ -180,6 +183,7 @@ dependencies = [
|
|||
"resources 0.1.0",
|
||||
"sync 0.1.0",
|
||||
"tempfile 3.0.7",
|
||||
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"vhost 0.1.0",
|
||||
"vm_control 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)",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "getopts"
|
||||
version = "0.2.18"
|
||||
|
@ -540,6 +556,11 @@ dependencies = [
|
|||
"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]]
|
||||
name = "memchr"
|
||||
version = "2.3.0"
|
||||
|
@ -609,6 +630,14 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num_cpus"
|
||||
version = "1.9.0"
|
||||
|
@ -893,6 +922,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"base 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",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"msg_socket 0.1.0",
|
||||
|
@ -931,11 +961,13 @@ dependencies = [
|
|||
"base 0.1.0",
|
||||
"data_model 0.1.0",
|
||||
"devices 0.1.0",
|
||||
"gdbstub 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hypervisor 0.1.0",
|
||||
"kernel_cmdline 0.1.0",
|
||||
"kernel_loader 0.1.0",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"minijail 0.2.1",
|
||||
"msg_socket 0.1.0",
|
||||
"remain 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"resources 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-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 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 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 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 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 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"
|
||||
|
|
|
@ -45,6 +45,7 @@ x = ["devices/x"]
|
|||
virtio-gpu-next = ["gpu_renderer/virtio-gpu-next"]
|
||||
composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
|
||||
gfxstream = ["devices/gfxstream"]
|
||||
gdb = ["gdbstub", "thiserror", "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
|
||||
|
||||
[dependencies]
|
||||
arch = { path = "arch" }
|
||||
|
@ -57,6 +58,7 @@ data_model = "*"
|
|||
devices = { path = "devices" }
|
||||
disk = { path = "disk" }
|
||||
enumn = { path = "enumn" }
|
||||
gdbstub = { version = "0.4.0", optional = true }
|
||||
gpu_buffer = { path = "gpu_buffer", optional = true }
|
||||
gpu_renderer = { path = "gpu_renderer", optional = true }
|
||||
hypervisor = { path = "hypervisor" }
|
||||
|
@ -77,6 +79,7 @@ remain = "*"
|
|||
resources = { path = "resources" }
|
||||
sync = { path = "sync" }
|
||||
tempfile = "*"
|
||||
thiserror = { version = "1.0.20", optional = true }
|
||||
vhost = { path = "vhost" }
|
||||
vm_control = { path = "vm_control" }
|
||||
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
|
||||
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
|
||||
|
||||
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"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
gdb = ["gdbstub", "msg_socket"]
|
||||
|
||||
[dependencies]
|
||||
acpi_tables = { path = "../acpi_tables" }
|
||||
devices = { path = "../devices" }
|
||||
gdbstub = { version = "0.4.0", optional = true }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
kernel_cmdline = { path = "../kernel_cmdline" }
|
||||
libc = "*"
|
||||
minijail = "*"
|
||||
msg_socket = { path = "../msg_socket", optional = true }
|
||||
resources = { path = "../resources" }
|
||||
sync = { path = "../sync" }
|
||||
base = { path = "../base" }
|
||||
|
|
|
@ -27,9 +27,15 @@ use hypervisor::{IoEventAddress, Vm};
|
|||
use minijail::Minijail;
|
||||
use resources::{MmioType, SystemAllocator};
|
||||
use sync::Mutex;
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
use vm_control::VmControlRequestSocket;
|
||||
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
|
||||
|
||||
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"))]
|
||||
use {
|
||||
devices::IrqChipAArch64 as IrqChipArch,
|
||||
|
@ -85,6 +91,8 @@ pub struct VmComponents {
|
|||
pub acpi_sdts: Vec<SDT>,
|
||||
pub rt_cpus: Vec<usize>,
|
||||
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`.
|
||||
|
@ -105,6 +113,8 @@ pub struct RunnableLinuxVm<V: VmArch, Vcpu: VcpuArch, I: IrqChipArch> {
|
|||
pub pid_debug_label_map: BTreeMap<u32, String>,
|
||||
pub suspend_evt: Event,
|
||||
pub rt_cpus: Vec<usize>,
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
pub gdb: Option<(u32, VmControlRequestSocket)>,
|
||||
}
|
||||
|
||||
/// The device and optional jail.
|
||||
|
@ -174,6 +184,32 @@ pub trait LinuxArch {
|
|||
has_bios: bool,
|
||||
no_smt: bool,
|
||||
) -> 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.
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//! configs.
|
||||
|
||||
pub mod argument;
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
pub mod gdb;
|
||||
#[path = "linux.rs"]
|
||||
pub mod platform;
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -215,6 +217,8 @@ pub struct Config {
|
|||
pub acpi_tables: Vec<PathBuf>,
|
||||
pub protected_vm: bool,
|
||||
pub battery_type: Option<BatteryType>,
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
pub gdb: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -271,6 +275,8 @@ impl Default for Config {
|
|||
acpi_tables: Vec::new(),
|
||||
protected_vm: false,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
187
src/linux.rs
187
src/linux.rs
|
@ -62,13 +62,17 @@ use base::{
|
|||
use vm_control::{
|
||||
BalloonControlCommand, BalloonControlRequestSocket, BalloonControlResponseSocket,
|
||||
BalloonControlResult, DiskControlCommand, DiskControlRequestSocket, DiskControlResponseSocket,
|
||||
DiskControlResult, IrqSetup, UsbControlSocket, VmControlResponseSocket, VmIrqRequest,
|
||||
VmIrqRequestSocket, VmIrqResponse, VmIrqResponseSocket, VmMemoryControlRequestSocket,
|
||||
VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse, VmMsyncRequest,
|
||||
VmMsyncRequestSocket, VmMsyncResponse, VmMsyncResponseSocket, VmRunMode,
|
||||
DiskControlResult, IrqSetup, UsbControlSocket, VcpuControl, VmControlResponseSocket,
|
||||
VmIrqRequest, VmIrqRequestSocket, VmIrqResponse, VmIrqResponseSocket,
|
||||
VmMemoryControlRequestSocket, VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse,
|
||||
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};
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
use crate::gdb::{gdb_thread, GdbStub};
|
||||
use crate::{Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption};
|
||||
use arch::{
|
||||
self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VcpuAffinity,
|
||||
|
@ -126,6 +130,8 @@ pub enum Error {
|
|||
FsDeviceNew(virtio::fs::Error),
|
||||
GetMaxOpenFiles(io::Error),
|
||||
GetSignalMask(signal::Error),
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
HandleDebugCommand(<Arch as LinuxArch>::Error),
|
||||
InputDeviceNew(virtio::InputError),
|
||||
InputEventsOpen(std::io::Error),
|
||||
InvalidFdPath,
|
||||
|
@ -160,11 +166,15 @@ pub enum Error {
|
|||
ResetTimer(base::Error),
|
||||
RngDeviceNew(virtio::RngError),
|
||||
RunnableVcpu(base::Error),
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
SendDebugStatus(Box<mpsc::SendError<VcpuDebugStatusMessage>>),
|
||||
SettingGidMap(minijail::Error),
|
||||
SettingMaxOpenFiles(minijail::Error),
|
||||
SettingSignalMask(base::Error),
|
||||
SettingUidMap(minijail::Error),
|
||||
SignalFd(base::SignalFdError),
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
SpawnGdbServer(io::Error),
|
||||
SpawnVcpu(io::Error),
|
||||
Timer(base::Error),
|
||||
ValidateRawFd(base::Error),
|
||||
|
@ -222,6 +232,8 @@ impl Display for Error {
|
|||
FsDeviceNew(e) => write!(f, "failed to create fs device: {}", 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),
|
||||
#[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),
|
||||
InputEventsOpen(e) => write!(f, "failed to open event device: {}", e),
|
||||
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),
|
||||
RngDeviceNew(e) => write!(f, "failed to set up rng: {}", 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),
|
||||
SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
|
||||
SettingSignalMask(e) => write!(f, "failed to set the signal mask for vcpu: {}", e),
|
||||
SettingUidMap(e) => write!(f, "error setting UID map: {}", 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),
|
||||
Timer(e) => write!(f, "failed to read timer fd: {}", e),
|
||||
ValidateRawFd(e) => write!(f, "failed to validate raw fd: {}", e),
|
||||
|
@ -300,10 +316,6 @@ enum TaggedControlSocket {
|
|||
VmMsync(VmMsyncResponseSocket),
|
||||
}
|
||||
|
||||
enum VcpuControl {
|
||||
RunState(VmRunMode),
|
||||
}
|
||||
|
||||
impl AsRef<UnixSeqpacket> for TaggedControlSocket {
|
||||
fn as_ref(&self) -> &UnixSeqpacket {
|
||||
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"))]
|
||||
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>(
|
||||
cpu_id: usize,
|
||||
vcpu: Option<V>,
|
||||
|
@ -1717,6 +1786,9 @@ fn run_vcpu<V>(
|
|||
requires_pvclock_ctrl: bool,
|
||||
from_main_channel: mpsc::Receiver<VcpuControl>,
|
||||
use_hypervisor_signals: bool,
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))] to_gdb_channel: Option<
|
||||
mpsc::Sender<VcpuDebugStatusMessage>,
|
||||
>,
|
||||
) -> Result<JoinHandle<()>>
|
||||
where
|
||||
V: VcpuArch + 'static,
|
||||
|
@ -1728,6 +1800,8 @@ where
|
|||
// implementation accomplishes that.
|
||||
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(
|
||||
cpu_id,
|
||||
vcpu,
|
||||
|
@ -1752,6 +1826,12 @@ where
|
|||
};
|
||||
|
||||
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;
|
||||
|
||||
'vcpu_loop: loop {
|
||||
|
@ -1765,7 +1845,7 @@ where
|
|||
Ok(m) => m,
|
||||
Err(mpsc::TryRecvError::Empty) if run_mode == VmRunMode::Running => {
|
||||
// If the VM is running and no message is pending, the state won't
|
||||
// be changed.
|
||||
// change.
|
||||
break 'state_loop;
|
||||
}
|
||||
Err(mpsc::TryRecvError::Empty) => {
|
||||
|
@ -1788,16 +1868,19 @@ where
|
|||
let mut messages = vec![msg];
|
||||
messages.append(&mut from_main_channel.try_iter().collect());
|
||||
|
||||
for VcpuControl::RunState(new_mode) in messages {
|
||||
run_mode = new_mode.clone();
|
||||
for msg in messages {
|
||||
match msg {
|
||||
VcpuControl::RunState(new_mode) => {
|
||||
run_mode = new_mode;
|
||||
match run_mode {
|
||||
VmRunMode::Running => break 'state_loop,
|
||||
VmRunMode::Suspending => {
|
||||
// On KVM implementations that use a paravirtualized clock (e.g.
|
||||
// x86), a flag must be set to indicate to the guest kernel that a
|
||||
// VCPU was suspended. The guest kernel will use this flag to
|
||||
// prevent the soft lockup detection from triggering when this VCPU
|
||||
// resumes, which could happen days later in realtime.
|
||||
// On KVM implementations that use a paravirtualized
|
||||
// clock (e.g. x86), a flag must be set to indicate to
|
||||
// the guest kernel that a vCPU was suspended. The guest
|
||||
// kernel will use this flag to prevent the soft lockup
|
||||
// detection from triggering when this vCPU resumes,
|
||||
// which could happen days later in realtime.
|
||||
if requires_pvclock_ctrl {
|
||||
if let Err(e) = vcpu.pvclock_ctrl() {
|
||||
error!(
|
||||
|
@ -1807,9 +1890,27 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2010,6 +2111,18 @@ where
|
|||
_ => 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 {
|
||||
memory_size: cfg
|
||||
.memory
|
||||
|
@ -2036,6 +2149,8 @@ where
|
|||
.collect::<Result<Vec<SDT>>>()?,
|
||||
rt_cpus: cfg.rt_cpus.clone(),
|
||||
protected_vm: cfg.protected_vm,
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
gdb: gdb_socket,
|
||||
};
|
||||
|
||||
let control_server_socket = match &cfg.socket_path {
|
||||
|
@ -2045,7 +2160,6 @@ where
|
|||
None => None,
|
||||
};
|
||||
|
||||
let mut control_sockets = Vec::new();
|
||||
let (wayland_host_socket, wayland_device_socket) =
|
||||
msg_socket::pair::<VmMemoryResponse, VmMemoryRequest>().map_err(Error::CreateSocket)?;
|
||||
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)?;
|
||||
}
|
||||
|
||||
#[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 vcpu_thread_barrier = Arc::new(Barrier::new(linux.vcpu_count + 1));
|
||||
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),
|
||||
from_main_channel,
|
||||
use_hypervisor_signals,
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
to_gdb_channel.clone(),
|
||||
)?;
|
||||
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();
|
||||
|
||||
'wait: loop {
|
||||
|
@ -2426,14 +2569,14 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static, I: IrqChipArch + '
|
|||
VmRunMode::Exiting => {
|
||||
break 'wait;
|
||||
}
|
||||
VmRunMode::Suspending | VmRunMode::Running => {
|
||||
if run_mode == VmRunMode::Suspending {
|
||||
other => {
|
||||
if other == VmRunMode::Suspending {
|
||||
linux.io_bus.notify_resume();
|
||||
}
|
||||
for (handle, channel) in &vcpu_handles {
|
||||
if let Err(e) = channel.send(
|
||||
VcpuControl::RunState(VmRunMode::Running),
|
||||
) {
|
||||
if let Err(e) = channel
|
||||
.send(VcpuControl::RunState(other.clone()))
|
||||
{
|
||||
error!("failed to send VmRunMode: {}", e);
|
||||
}
|
||||
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)?;
|
||||
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),
|
||||
_ => 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);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1627,6 +1645,7 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
|
|||
Possible key values:
|
||||
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.")];
|
||||
|
||||
let mut cfg = Config::default();
|
||||
|
|
|
@ -4,8 +4,12 @@ version = "0.1.0"
|
|||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
gdb = ["gdbstub"]
|
||||
|
||||
[dependencies]
|
||||
data_model = { path = "../data_model" }
|
||||
gdbstub = { version = "0.4.0", optional = true }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
libc = "*"
|
||||
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
|
||||
//! if the request type expects one.
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
pub mod gdb;
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::fs::File;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
|
@ -31,8 +34,18 @@ use resources::{Alloc, GpuMemoryDesc, MmioType, SystemAllocator};
|
|||
use sync::Mutex;
|
||||
use vm_memory::GuestAddress;
|
||||
|
||||
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
|
||||
pub use crate::gdb::*;
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
pub enum MaybeOwnedDescriptor {
|
||||
|
@ -102,6 +115,8 @@ pub enum VmRunMode {
|
|||
Suspending,
|
||||
/// Indicates that the VM is exiting all processes.
|
||||
Exiting,
|
||||
/// Indicates that the VM is in a breakpoint waiting for the debugger to do continue.
|
||||
Breakpoint,
|
||||
}
|
||||
|
||||
impl Display for VmRunMode {
|
||||
|
@ -112,6 +127,7 @@ impl Display for VmRunMode {
|
|||
Running => write!(f, "running"),
|
||||
Suspending => write!(f, "suspending"),
|
||||
Exiting => write!(f, "exiting"),
|
||||
Breakpoint => write!(f, "breakpoint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,20 @@ version = "0.1.0"
|
|||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
gdb = ["gdbstub", "msg_socket", "arch/gdb"]
|
||||
|
||||
[dependencies]
|
||||
arch = { path = "../arch" }
|
||||
assertions = { path = "../assertions" }
|
||||
data_model = { path = "../data_model" }
|
||||
devices = { path = "../devices" }
|
||||
gdbstub = { version = "0.4.0", optional = true }
|
||||
hypervisor = { path = "../hypervisor" }
|
||||
kernel_cmdline = { path = "../kernel_cmdline" }
|
||||
kernel_loader = { path = "../kernel_loader" }
|
||||
libc = "*"
|
||||
msg_socket = { path = "../msg_socket", optional = true }
|
||||
minijail = "*"
|
||||
remain = "*"
|
||||
resources = { path = "../resources" }
|
||||
|
|
|
@ -69,6 +69,11 @@ use resources::SystemAllocator;
|
|||
use sync::Mutex;
|
||||
use vm_control::BatteryType;
|
||||
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]
|
||||
#[derive(Debug)]
|
||||
|
@ -100,7 +105,10 @@ pub enum Error {
|
|||
LoadCmdline(kernel_loader::Error),
|
||||
LoadInitrd(arch::LoadImageError),
|
||||
LoadKernel(kernel_loader::Error),
|
||||
PageNotPresent,
|
||||
Pstore(arch::pstore::Error),
|
||||
ReadingGuestMemory(vm_memory::GuestMemoryError),
|
||||
ReadRegs(base::Error),
|
||||
RegisterIrqfd(base::Error),
|
||||
RegisterVsock(arch::DeviceRegistrationError),
|
||||
SetLint(interrupts::Error),
|
||||
|
@ -113,6 +121,9 @@ pub enum Error {
|
|||
SetupRegs(regs::Error),
|
||||
SetupSmbios(smbios::Error),
|
||||
SetupSregs(regs::Error),
|
||||
TranslatingVirtAddr,
|
||||
WriteRegs(base::Error),
|
||||
WritingGuestMemory(GuestMemoryError),
|
||||
ZeroPagePastRamEnd,
|
||||
ZeroPageSetup,
|
||||
}
|
||||
|
@ -151,7 +162,10 @@ impl Display for Error {
|
|||
LoadCmdline(e) => write!(f, "error loading command line: {}", e),
|
||||
LoadInitrd(e) => write!(f, "error loading initrd: {}", 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),
|
||||
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),
|
||||
RegisterVsock(e) => write!(f, "error registering virtual socket device: {}", 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),
|
||||
SetupSmbios(e) => write!(f, "failed to set up SMBIOS: {}", 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"),
|
||||
ZeroPageSetup => write!(f, "error writing the zero page of guest memory"),
|
||||
}
|
||||
|
@ -493,6 +510,8 @@ impl arch::LinuxArch for X8664arch {
|
|||
pid_debug_label_map,
|
||||
suspend_evt,
|
||||
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(())
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
|
Loading…
Reference in a new issue