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:
Keiichi Watanabe 2020-10-21 15:57:33 +09:00 committed by Commit Bot
parent fa149aba03
commit c5262e9fad
14 changed files with 850 additions and 33 deletions

35
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

View file

@ -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.

View file

@ -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" }

View file

@ -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.

View file

@ -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
View 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)
}
}
}
}

View file

@ -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, &regs).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);

View file

@ -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();

View file

@ -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
View 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,
}

View file

@ -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"),
}
}
}

View file

@ -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" }

View file

@ -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 {