This change introduces initial support for tracing and logging in Rust-based QEMU code. As an example, tracing and logging have been implemented in the pl011 device, which is written in Rust.
- Updated `rust/wrapper.h` to include the `qemu/log.h` and `hw/char/trace.h` header. - Added log.rs to wrap `qemu_log_mask` and `qemu_log_mask_and_addr` - Modified `tracetool` scripts to move C function implementation from header to .c - Added log and trace in rust version of PL011 device Future enhancements could include generating idiomatic Rust APIs for tracing using the tracetool scripts Signed-off-by: saman <sa...@enumclass.cc> --- include/qemu/log-for-trace.h | 5 +-- rust/hw/char/pl011/src/device.rs | 34 +++++++++++++++--- rust/hw/char/pl011/src/registers.rs | 20 +++++++++++ rust/qemu-api/meson.build | 1 + rust/qemu-api/src/lib.rs | 1 + rust/qemu-api/src/log.rs | 54 +++++++++++++++++++++++++++++ rust/wrapper.h | 2 ++ scripts/tracetool/format/c.py | 16 +++++++++ scripts/tracetool/format/h.py | 11 ++---- util/log.c | 5 +++ 10 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 rust/qemu-api/src/log.rs diff --git a/include/qemu/log-for-trace.h b/include/qemu/log-for-trace.h index d47c9cd446..ad5cd0dd24 100644 --- a/include/qemu/log-for-trace.h +++ b/include/qemu/log-for-trace.h @@ -24,10 +24,7 @@ extern int qemu_loglevel; #define LOG_TRACE (1 << 15) /* Returns true if a bit is set in the current loglevel mask */ -static inline bool qemu_loglevel_mask(int mask) -{ - return (qemu_loglevel & mask) != 0; -} +bool qemu_loglevel_mask(int mask); /* main logging function */ void G_GNUC_PRINTF(1, 2) qemu_log(const char *fmt, ...); diff --git a/rust/hw/char/pl011/src/device.rs b/rust/hw/char/pl011/src/device.rs index bf88e0b00a..42385a7bf6 100644 --- a/rust/hw/char/pl011/src/device.rs +++ b/rust/hw/char/pl011/src/device.rs @@ -2,15 +2,21 @@ // Author(s): Manos Pitsidianakis <manos.pitsidiana...@linaro.org> // SPDX-License-Identifier: GPL-2.0-or-later -use std::{ffi::CStr, mem::size_of, ptr::addr_of_mut}; +use std::{ + ffi::{CStr, CString}, + mem::size_of, + ptr::addr_of_mut, +}; use qemu_api::{ chardev::{CharBackend, Chardev, Event}, impl_vmstate_forward, irq::{IRQState, InterruptSource}, + log::Mask, memory::{hwaddr, MemoryRegion, MemoryRegionOps, MemoryRegionOpsBuilder}, prelude::*, qdev::{Clock, ClockEvent, DeviceImpl, DeviceState, Property, ResetType, ResettablePhasesImpl}, + qemu_log_mask, qom::{ObjectImpl, Owned, ParentField}, static_assert, sysbus::{SysBusDevice, SysBusDeviceImpl}, @@ -298,7 +304,7 @@ pub(self) fn write( DMACR => { self.dmacr = value; if value & 3 > 0 { - // qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n"); + qemu_log_mask!(Mask::log_unimp, "pl011: DMA not implemented\n"); eprintln!("pl011: DMA not implemented"); } } @@ -535,11 +541,21 @@ fn read(&self, offset: hwaddr, _size: u32) -> u64 { u64::from(device_id[(offset - 0xfe0) >> 2]) } Err(_) => { - // qemu_log_mask(LOG_GUEST_ERROR, "pl011_read: Bad offset 0x%x\n", (int)offset); + qemu_log_mask!( + Mask::log_guest_error, + "pl011_read: Bad offset 0x%x\n", + offset as i32 + ); 0 } Ok(field) => { + let regname = field.as_str(); let (update_irq, result) = self.regs.borrow_mut().read(field); + let c_string = CString::new(regname).expect("CString::new failed"); + let name_ptr = c_string.as_ptr(); + unsafe { + qemu_api::bindings::trace_pl011_read(offset as u32, result, name_ptr); + } if update_irq { self.update(); self.char_backend.accept_input(); @@ -576,8 +592,16 @@ fn write(&self, offset: hwaddr, value: u64, _size: u32) { fn can_receive(&self) -> u32 { let regs = self.regs.borrow(); - // trace_pl011_can_receive(s->lcr, s->read_count, r); - u32::from(regs.read_count < regs.fifo_depth()) + let fifo_available = u32::from(regs.read_count < regs.fifo_depth()); + unsafe { + qemu_api::bindings::trace_pl011_can_receive( + u32::from(regs.line_control), + regs.read_count, + regs.fifo_depth().try_into().unwrap(), + fifo_available, + ); + } + fifo_available } fn receive(&self, buf: &[u8]) { diff --git a/rust/hw/char/pl011/src/registers.rs b/rust/hw/char/pl011/src/registers.rs index cd92fa2c30..11c085030d 100644 --- a/rust/hw/char/pl011/src/registers.rs +++ b/rust/hw/char/pl011/src/registers.rs @@ -72,6 +72,26 @@ pub enum RegisterOffset { //Reserved = 0x04C, } +impl RegisterOffset { + pub fn as_str(&self) -> &'static str { + match self { + RegisterOffset::DR => "DR", + RegisterOffset::RSR => "RSR", + RegisterOffset::FR => "FR", + RegisterOffset::FBRD => "FBRD", + RegisterOffset::ILPR => "ILPR", + RegisterOffset::IBRD => "IBRD", + RegisterOffset::LCR_H => "LCR_H", + RegisterOffset::CR => "CR", + RegisterOffset::FLS => "FLS", + RegisterOffset::IMSC => "IMSC", + RegisterOffset::RIS => "RIS", + RegisterOffset::MIS => "MIS", + RegisterOffset::ICR => "ICR", + RegisterOffset::DMACR => "DMACR", + } + } +} /// Receive Status Register / Data Register common error bits /// /// The `UARTRSR` register is updated only when a read occurs diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 858685ddd4..f8eddf7887 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -34,6 +34,7 @@ _qemu_api_rs = static_library( 'src/qom.rs', 'src/sysbus.rs', 'src/timer.rs', + 'src/log.rs', 'src/vmstate.rs', 'src/zeroable.rs', ], diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index 05f38b51d3..b54989a243 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -21,6 +21,7 @@ pub mod chardev; pub mod errno; pub mod irq; +pub mod log; pub mod memory; pub mod module; pub mod offset_of; diff --git a/rust/qemu-api/src/log.rs b/rust/qemu-api/src/log.rs new file mode 100644 index 0000000000..07e8bceb34 --- /dev/null +++ b/rust/qemu-api/src/log.rs @@ -0,0 +1,54 @@ +#[allow(non_camel_case_types)] +#[repr(u32)] +pub enum Mask { + cpu_log_tb_out_asm = crate::bindings::CPU_LOG_TB_OUT_ASM, + cpu_log_tb_in_asm = crate::bindings::CPU_LOG_TB_IN_ASM, + cpu_log_tb_op = crate::bindings::CPU_LOG_TB_OP, + cpu_log_tb_op_opt = crate::bindings::CPU_LOG_TB_OP_OPT, + cpu_log_int = crate::bindings::CPU_LOG_INT, + cpu_log_exec = crate::bindings::CPU_LOG_EXEC, + cpu_log_pcall = crate::bindings::CPU_LOG_PCALL, + cpu_log_tb_cpu = crate::bindings::CPU_LOG_TB_CPU, + cpu_log_reset = crate::bindings::CPU_LOG_RESET, + log_unimp = crate::bindings::LOG_UNIMP, + log_guest_error = crate::bindings::LOG_GUEST_ERROR, + cpu_log_mmu = crate::bindings::CPU_LOG_MMU, + cpu_log_tb_nochain = crate::bindings::CPU_LOG_TB_NOCHAIN, + cpu_log_page = crate::bindings::CPU_LOG_PAGE, + cpu_log_tb_op_ind = crate::bindings::CPU_LOG_TB_OP_IND, + cpu_log_tb_fpu = crate::bindings::CPU_LOG_TB_FPU, + cpu_log_plugin = crate::bindings::CPU_LOG_PLUGIN, + log_strace = crate::bindings::LOG_STRACE, + log_per_thread = crate::bindings::LOG_PER_THREAD, + cpu_log_tb_vpu = crate::bindings::CPU_LOG_TB_VPU, + log_tb_op_plugin = crate::bindings::LOG_TB_OP_PLUGIN, + log_invalid_mem = crate::bindings::LOG_INVALID_MEM, +} + +#[macro_export] +macro_rules! qemu_log_mask { + ($mask:expr, $fmt:expr $(, $arg:expr)*) => {{ + let mask: Mask = $mask; + unsafe { + if $crate::bindings::qemu_loglevel_mask(mask as std::os::raw::c_int) { + let format_str = std::ffi::CString::new($fmt).expect("CString::new failed"); + $crate::bindings::qemu_log(format_str.as_ptr() $(, $arg)*); + } + } + }}; +} + +#[macro_export] +macro_rules! qemu_log_mask_and_addr { + ($mask:expr, $addr:expr, $fmt:expr $(, $arg:expr)*) => {{ + let mask: Mask = $mask; + let addr: $crate::bindings::hwaddr = $addr; + unsafe { + if $crate::bindings::qemu_loglevel_mask(mask as std::os::raw::c_int) && + $crate::bindings::qemu_log_in_addr_range(addr) { + let format_str = std::ffi::CString::new($fmt).expect("CString::new failed"); + $crate::bindings::qemu_log(format_str.as_ptr() $(, $arg)*); + } + } + }}; +} diff --git a/rust/wrapper.h b/rust/wrapper.h index d4fec54657..cd2f311d71 100644 --- a/rust/wrapper.h +++ b/rust/wrapper.h @@ -64,5 +64,7 @@ typedef enum memory_order { #include "chardev/char-serial.h" #include "exec/memattrs.h" #include "qemu/timer.h" +#include "qemu/log.h" #include "exec/address-spaces.h" #include "hw/char/pl011.h" +#include "hw/char/trace.h" diff --git a/scripts/tracetool/format/c.py b/scripts/tracetool/format/c.py index 69edf0d588..f2d383f89c 100644 --- a/scripts/tracetool/format/c.py +++ b/scripts/tracetool/format/c.py @@ -43,6 +43,22 @@ def generate(events, backend, group): sstate = "TRACE_%s_ENABLED" % e.name.upper(), dstate = e.api(e.QEMU_DSTATE)) + cond = "true" + + out('', + 'void %(api)s(%(args)s)', + '{', + ' if (%(cond)s) {', + ' %(api_nocheck)s(%(names)s);', + ' }', + '}', + api=e.api(), + api_nocheck=e.api(e.QEMU_TRACE_NOCHECK), + args=e.args, + names=", ".join(e.args.names()), + cond=cond + ) + out('TraceEvent *%(group)s_trace_events[] = {', group = group.lower()) diff --git a/scripts/tracetool/format/h.py b/scripts/tracetool/format/h.py index ea126b07ea..16b360ae49 100644 --- a/scripts/tracetool/format/h.py +++ b/scripts/tracetool/format/h.py @@ -74,17 +74,10 @@ def generate(events, backend, group): cond = "true" out('', - 'static inline void %(api)s(%(args)s)', - '{', - ' if (%(cond)s) {', - ' %(api_nocheck)s(%(names)s);', - ' }', - '}', + 'void %(api)s(%(args)s);', api=e.api(), - api_nocheck=e.api(e.QEMU_TRACE_NOCHECK), args=e.args, - names=", ".join(e.args.names()), - cond=cond) + ) backend.generate_end(events, group) diff --git a/util/log.c b/util/log.c index b87d399e4c..51f659be0a 100644 --- a/util/log.c +++ b/util/log.c @@ -143,6 +143,11 @@ void qemu_log_unlock(FILE *logfile) } } +bool qemu_loglevel_mask(int mask) +{ + return (qemu_loglevel & mask) != 0; +} + void qemu_log(const char *fmt, ...) { FILE *f = qemu_log_trylock(); -- 2.43.0