From e9179a797e06c6fcf2471bfd4fb83366ffdd111a Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 12:18:33 +0800 Subject: [PATCH 01/10] Keccak permute --- Cargo.lock | 1 + ceno_emul/Cargo.toml | 1 + ceno_emul/src/syscalls.rs | 2 +- ceno_rt/src/lib.rs | 5 +---- ceno_rt/src/syscalls.rs | 12 +++++++----- examples/examples/ceno_rt_keccak.rs | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 355c8472c..06ccf828b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,7 @@ name = "ceno_emul" version = "0.1.0" dependencies = [ "anyhow", + "ceno_rt", "elf", "itertools 0.13.0", "num-derive", diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 4f4f4a7c7..2bc4c830f 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -11,6 +11,7 @@ version.workspace = true [dependencies] anyhow.workspace = true +ceno_rt = { path = "../ceno_rt" } elf = "0.7" itertools.workspace = true num-derive.workspace = true diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index 251bedbae..d5ca85402 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -6,7 +6,7 @@ pub mod keccak_permute; // Using the same function codes as sp1: // https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/code.rs -pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; +pub use ceno_rt::syscalls::KECCAK_PERMUTE; /// Trace the inputs and effects of a syscall. pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result { diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 576615802..b48279d69 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -22,10 +22,7 @@ pub use io::info_out; mod params; pub use params::*; -#[cfg(target_arch = "riscv32")] -mod syscalls; -#[cfg(target_arch = "riscv32")] -pub use syscalls::*; +pub mod syscalls; #[no_mangle] #[linkage = "weak"] diff --git a/ceno_rt/src/syscalls.rs b/ceno_rt/src/syscalls.rs index 90ace85da..1ebef70bc 100644 --- a/ceno_rt/src/syscalls.rs +++ b/ceno_rt/src/syscalls.rs @@ -1,9 +1,9 @@ // Based on https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/zkvm/entrypoint/src/syscalls/keccak_permute.rs - -const KECCAK_PERMUTE: u32 = 0x00_01_01_09; - +#[cfg(target_os = "zkvm")] use core::arch::asm; +pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; + /// Executes the Keccak256 permutation on the given state. /// /// ### Safety @@ -11,8 +11,8 @@ use core::arch::asm; /// The caller must ensure that `state` is valid pointer to data that is aligned along a four /// byte boundary. #[allow(unused_variables)] -#[no_mangle] -pub extern "C" fn syscall_keccak_permute(state: &mut [u64; 25]) { +pub fn keccak_permute(state: &mut [u64; 25]) { + #[cfg(target_os = "zkvm")] unsafe { asm!( "ecall", @@ -21,4 +21,6 @@ pub extern "C" fn syscall_keccak_permute(state: &mut [u64; 25]) { in("a1") 0 ); } + #[cfg(not(target_os = "zkvm"))] + unreachable!() } diff --git a/examples/examples/ceno_rt_keccak.rs b/examples/examples/ceno_rt_keccak.rs index 7a01557bf..57d9f76ed 100644 --- a/examples/examples/ceno_rt_keccak.rs +++ b/examples/examples/ceno_rt_keccak.rs @@ -3,7 +3,7 @@ //! Iterate multiple times and log the state after each iteration. extern crate ceno_rt; -use ceno_rt::{info_out, syscall_keccak_permute}; +use ceno_rt::{info_out, syscalls::keccak_permute}; use core::slice; const ITERATIONS: usize = 3; @@ -12,7 +12,7 @@ fn main() { let mut state = [0_u64; 25]; for _ in 0..ITERATIONS { - syscall_keccak_permute(&mut state); + keccak_permute(&mut state); log_state(&state); } } From 17c7ef40b711cd1aefcfa38fe61505f0db2048f7 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 12:19:54 +0800 Subject: [PATCH 02/10] Examples, too --- Cargo.lock | 8 ++++++++ Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 06ccf828b..38ceb9738 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -711,6 +711,14 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "examples" +version = "0.1.0" +dependencies = [ + "ceno_rt", + "rkyv", +] + [[package]] name = "fastrand" version = "2.3.0" diff --git a/Cargo.toml b/Cargo.toml index 04c31fd15..0a5361732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] -exclude = ["examples"] members = [ "ceno_emul", "ceno_host", "ceno_rt", "ceno_zkvm", "examples-builder", + "examples", "mpcs", "multilinear_extensions", "poseidon", From c700bd6e10c2f6b16756e75f798350b3c1d1a622 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 12:27:59 +0800 Subject: [PATCH 03/10] Fix target spec --- examples-builder/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples-builder/build.rs b/examples-builder/build.rs index b6dee0cfe..ba3c6e6df 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -56,7 +56,7 @@ fn build_elfs() { dest, r#"#[allow(non_upper_case_globals)] pub const {example}: &[u8] = - include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-ceno-zkvm-elf/release/examples/{example}");"# + include_bytes!(r"{CARGO_MANIFEST_DIR}/../target/riscv32im-ceno-zkvm-elf/release/examples/{example}");"# ).expect("failed to write vars.rs"); } rerun_all_but_target(Path::new("../examples")); From f877f6b10b200446a8c8b9fe30b841394f07312d Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 13:23:44 +0800 Subject: [PATCH 04/10] Start of debugger work, compiles, but doesn't do anything --- Cargo.lock | 32 +++++++++++++ ceno_emul/Cargo.toml | 2 + ceno_emul/src/gdb.rs | 106 +++++++++++++++++++++++++++++++++++++++++++ ceno_emul/src/lib.rs | 2 + 4 files changed, 142 insertions(+) create mode 100644 ceno_emul/src/gdb.rs diff --git a/Cargo.lock b/Cargo.lock index 38ceb9738..ffa0c3adc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,8 @@ dependencies = [ "anyhow", "ceno_rt", "elf", + "gdbstub", + "gdbstub_arch", "itertools 0.13.0", "num-derive", "num-traits", @@ -779,6 +781,30 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "gdbstub" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c683a9f13de31432e6097131d5f385898c7f0635c0f392b9d0fa165063c8ac" +dependencies = [ + "bitflags", + "cfg-if", + "log", + "managed", + "num-traits", + "paste", +] + +[[package]] +name = "gdbstub_arch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328a9e9425db13770d0d11de6332a608854266e44c53d12776be7b4aa427e3de" +dependencies = [ + "gdbstub", + "num-traits", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1081,6 +1107,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "matchers" version = "0.1.0" diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 2bc4c830f..611dbfd7c 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -13,6 +13,8 @@ version.workspace = true anyhow.workspace = true ceno_rt = { path = "../ceno_rt" } elf = "0.7" +gdbstub = "0.7.3" +gdbstub_arch = "0.3.1" itertools.workspace = true num-derive.workspace = true num-traits.workspace = true diff --git a/ceno_emul/src/gdb.rs b/ceno_emul/src/gdb.rs new file mode 100644 index 000000000..3dcb8713a --- /dev/null +++ b/ceno_emul/src/gdb.rs @@ -0,0 +1,106 @@ +use gdbstub::common::Signal; +use gdbstub::target::{Target, TargetResult}; +use gdbstub::target::ext::base::BaseOps; +use gdbstub::target::ext::base::singlethread::{ + SingleThreadResumeOps, SingleThreadSingleStepOps +}; +use gdbstub::target::ext::base::singlethread::{ + SingleThreadBase, SingleThreadResume, SingleThreadSingleStep +}; +use gdbstub::target::ext::breakpoints::{Breakpoints, SwBreakpoint}; +use gdbstub::target::ext::breakpoints::{BreakpointsOps, SwBreakpointOps}; + +struct MyTarget; + +impl Target for MyTarget { + type Error = (); + type Arch = gdbstub_arch::arm::Armv4t; // as an example + + #[inline(always)] + fn base_ops(&mut self) -> BaseOps { + BaseOps::SingleThread(self) + } + + // opt-in to support for setting/removing breakpoints + #[inline(always)] + fn support_breakpoints(&mut self) -> Option> { + Some(self) + } +} + +impl SingleThreadBase for MyTarget { + fn read_registers( + &mut self, + regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, + ) -> TargetResult<(), Self> { todo!() } + + fn write_registers( + &mut self, + regs: &gdbstub_arch::arm::reg::ArmCoreRegs + ) -> TargetResult<(), Self> { todo!() } + + fn read_addrs( + &mut self, + start_addr: u32, + data: &mut [u8], + ) -> TargetResult { todo!() } + + fn write_addrs( + &mut self, + start_addr: u32, + data: &[u8], + ) -> TargetResult<(), Self> { todo!() } + + // most targets will want to support at resumption as well... + + #[inline(always)] + fn support_resume(&mut self) -> Option> { + Some(self) + } +} + +impl SingleThreadResume for MyTarget { + fn resume( + &mut self, + signal: Option, + ) -> Result<(), Self::Error> { todo!() } + + // ...and if the target supports resumption, it'll likely want to support + // single-step resume as well + + #[inline(always)] + fn support_single_step( + &mut self + ) -> Option> { + Some(self) + } +} + +impl SingleThreadSingleStep for MyTarget { + fn step( + &mut self, + signal: Option, + ) -> Result<(), Self::Error> { todo!() } +} + +impl Breakpoints for MyTarget { + // there are several kinds of breakpoints - this target uses software breakpoints + #[inline(always)] + fn support_sw_breakpoint(&mut self) -> Option> { + Some(self) + } +} + +impl SwBreakpoint for MyTarget { + fn add_sw_breakpoint( + &mut self, + addr: u32, + kind: gdbstub_arch::arm::ArmBreakpointKind, + ) -> TargetResult { todo!() } + + fn remove_sw_breakpoint( + &mut self, + addr: u32, + kind: gdbstub_arch::arm::ArmBreakpointKind, + ) -> TargetResult { todo!() } +} diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index 5e9d871ef..3fda128bb 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -28,3 +28,5 @@ pub use syscalls::{KECCAK_PERMUTE, keccak_permute::KECCAK_WORDS}; pub mod test_utils; pub mod host_utils; + +pub mod gdb; From 272e91d9ab03deb33981376326b9c373ecb912c4 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 13:29:20 +0800 Subject: [PATCH 05/10] With RiscV scaffolding --- ceno_emul/src/gdb.rs | 90 ++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/ceno_emul/src/gdb.rs b/ceno_emul/src/gdb.rs index 3dcb8713a..7b1c09170 100644 --- a/ceno_emul/src/gdb.rs +++ b/ceno_emul/src/gdb.rs @@ -1,20 +1,27 @@ -use gdbstub::common::Signal; -use gdbstub::target::{Target, TargetResult}; -use gdbstub::target::ext::base::BaseOps; -use gdbstub::target::ext::base::singlethread::{ - SingleThreadResumeOps, SingleThreadSingleStepOps +use gdbstub::{ + arch::{Arch, BreakpointKind}, + common::Signal, + target::{ + Target, TargetResult, + ext::{ + base::{ + BaseOps, + singlethread::{ + SingleThreadBase, SingleThreadResume, SingleThreadResumeOps, + SingleThreadSingleStep, SingleThreadSingleStepOps, + }, + }, + breakpoints::{Breakpoints, BreakpointsOps, SwBreakpoint, SwBreakpointOps}, + }, + }, }; -use gdbstub::target::ext::base::singlethread::{ - SingleThreadBase, SingleThreadResume, SingleThreadSingleStep -}; -use gdbstub::target::ext::breakpoints::{Breakpoints, SwBreakpoint}; -use gdbstub::target::ext::breakpoints::{BreakpointsOps, SwBreakpointOps}; +use gdbstub_arch::riscv::Riscv32; -struct MyTarget; +pub struct MyTarget; impl Target for MyTarget { type Error = (); - type Arch = gdbstub_arch::arm::Armv4t; // as an example + type Arch = gdbstub_arch::riscv::Riscv32; #[inline(always)] fn base_ops(&mut self) -> BaseOps { @@ -31,25 +38,26 @@ impl Target for MyTarget { impl SingleThreadBase for MyTarget { fn read_registers( &mut self, - regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, - ) -> TargetResult<(), Self> { todo!() } + // regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, + regs: &mut gdbstub_arch::riscv::reg::RiscvCoreRegs, + ) -> TargetResult<(), Self> { + todo!() + } fn write_registers( &mut self, - regs: &gdbstub_arch::arm::reg::ArmCoreRegs - ) -> TargetResult<(), Self> { todo!() } + regs: &gdbstub_arch::riscv::reg::RiscvCoreRegs, + ) -> TargetResult<(), Self> { + todo!() + } - fn read_addrs( - &mut self, - start_addr: u32, - data: &mut [u8], - ) -> TargetResult { todo!() } + fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult { + todo!() + } - fn write_addrs( - &mut self, - start_addr: u32, - data: &[u8], - ) -> TargetResult<(), Self> { todo!() } + fn write_addrs(&mut self, start_addr: u32, data: &[u8]) -> TargetResult<(), Self> { + todo!() + } // most targets will want to support at resumption as well... @@ -60,27 +68,23 @@ impl SingleThreadBase for MyTarget { } impl SingleThreadResume for MyTarget { - fn resume( - &mut self, - signal: Option, - ) -> Result<(), Self::Error> { todo!() } + fn resume(&mut self, signal: Option) -> Result<(), Self::Error> { + todo!() + } // ...and if the target supports resumption, it'll likely want to support // single-step resume as well #[inline(always)] - fn support_single_step( - &mut self - ) -> Option> { + fn support_single_step(&mut self) -> Option> { Some(self) } } impl SingleThreadSingleStep for MyTarget { - fn step( - &mut self, - signal: Option, - ) -> Result<(), Self::Error> { todo!() } + fn step(&mut self, signal: Option) -> Result<(), Self::Error> { + todo!() + } } impl Breakpoints for MyTarget { @@ -95,12 +99,16 @@ impl SwBreakpoint for MyTarget { fn add_sw_breakpoint( &mut self, addr: u32, - kind: gdbstub_arch::arm::ArmBreakpointKind, - ) -> TargetResult { todo!() } + kind: ::BreakpointKind, + ) -> TargetResult { + todo!() + } fn remove_sw_breakpoint( &mut self, addr: u32, - kind: gdbstub_arch::arm::ArmBreakpointKind, - ) -> TargetResult { todo!() } + kind: ::BreakpointKind, + ) -> TargetResult { + todo!() + } } From 6d5060b6baeafe8bd452d5d5bb4ac7b42fd48ab2 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 13:46:30 +0800 Subject: [PATCH 06/10] More meat on the bone --- ceno_emul/src/gdb.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/ceno_emul/src/gdb.rs b/ceno_emul/src/gdb.rs index 7b1c09170..78ae6187a 100644 --- a/ceno_emul/src/gdb.rs +++ b/ceno_emul/src/gdb.rs @@ -11,16 +11,22 @@ use gdbstub::{ SingleThreadSingleStep, SingleThreadSingleStepOps, }, }, - breakpoints::{Breakpoints, BreakpointsOps, SwBreakpoint, SwBreakpointOps}, + breakpoints::{ + Breakpoints, BreakpointsOps, HwBreakpointOps, SwBreakpoint, SwBreakpointOps, + }, }, }, }; use gdbstub_arch::riscv::Riscv32; +use itertools::enumerate; + +use crate::{ByteAddr, EmuContext, VMState}; -pub struct MyTarget; +// This should probably reference / or be VMState? +pub struct MyTarget(VMState); impl Target for MyTarget { - type Error = (); + type Error = anyhow::Error; type Arch = gdbstub_arch::riscv::Riscv32; #[inline(always)] @@ -41,14 +47,22 @@ impl SingleThreadBase for MyTarget { // regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, regs: &mut gdbstub_arch::riscv::reg::RiscvCoreRegs, ) -> TargetResult<(), Self> { - todo!() + for (i, reg) in enumerate(&mut regs.x) { + *reg = self.0.peek_register(i); + } + regs.pc = u32::from(self.0.get_pc()); + Ok(()) } fn write_registers( &mut self, regs: &gdbstub_arch::riscv::reg::RiscvCoreRegs, ) -> TargetResult<(), Self> { - todo!() + for (i, reg) in enumerate(®s.x) { + self.0.init_register_unsafe(i, *reg); + } + self.0.set_pc(ByteAddr::from(regs.pc)); + Ok(()) } fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult { @@ -87,6 +101,7 @@ impl SingleThreadSingleStep for MyTarget { } } +// TODO: consider adding WatchKind, and perhaps hardware breakpoints? impl Breakpoints for MyTarget { // there are several kinds of breakpoints - this target uses software breakpoints #[inline(always)] From 9981aac5c0fd77f0d0077d1e9a5119a65d866582 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 15:19:14 +0800 Subject: [PATCH 07/10] implement more --- ceno_emul/src/gdb.rs | 116 +++++++++++++++++++++++++++++++++++++------ ceno_emul/src/lib.rs | 1 + 2 files changed, 103 insertions(+), 14 deletions(-) diff --git a/ceno_emul/src/gdb.rs b/ceno_emul/src/gdb.rs index 78ae6187a..bc105cb50 100644 --- a/ceno_emul/src/gdb.rs +++ b/ceno_emul/src/gdb.rs @@ -1,11 +1,16 @@ +use std::collections::BTreeSet; + use gdbstub::{ arch::{Arch, BreakpointKind}, common::Signal, target::{ - Target, TargetResult, + Target, + TargetError::NonFatal, + TargetResult, ext::{ base::{ BaseOps, + single_register_access::{SingleRegisterAccess, SingleRegisterAccessOps}, singlethread::{ SingleThreadBase, SingleThreadResume, SingleThreadResumeOps, SingleThreadSingleStep, SingleThreadSingleStepOps, @@ -17,13 +22,16 @@ use gdbstub::{ }, }, }; -use gdbstub_arch::riscv::Riscv32; -use itertools::enumerate; +use gdbstub_arch::riscv::{Riscv32, reg::id::RiscvRegId}; +use itertools::{Itertools, enumerate}; -use crate::{ByteAddr, EmuContext, VMState}; +use crate::{ByteAddr, EmuContext, RegIdx, VMState, WordAddr}; // This should probably reference / or be VMState? -pub struct MyTarget(VMState); +pub struct MyTarget { + state: VMState, + break_points: BTreeSet<(u32, ::BreakpointKind)>, +} impl Target for MyTarget { type Error = anyhow::Error; @@ -41,16 +49,64 @@ impl Target for MyTarget { } } +// TODO: add SingleRegisterAccess +impl SingleRegisterAccess<()> for MyTarget { + fn read_register( + &mut self, + _thread_id: (), + reg_id: ::RegId, + buf: &mut [u8], + ) -> TargetResult { + match reg_id { + RiscvRegId::Gpr(i) if (0..32).contains(&i) => { + buf.copy_from_slice(&self.state.peek_register(RegIdx::from(i)).to_le_bytes()); + Ok(4) + } + RiscvRegId::Pc => { + buf.copy_from_slice(&self.state.get_pc().0.to_le_bytes()); + Ok(4) + } + // TODO(Matthias): see whether we can make this more specific. + _ => Err(NonFatal), + } + } + + fn write_register( + &mut self, + _thread_id: (), + reg_id: ::RegId, + value: &[u8], + ) -> TargetResult<(), Self> { + let mut bytes = [0; 4]; + bytes.copy_from_slice(value); + let buf = u32::from_le_bytes(bytes); + match reg_id { + // Note: we refuse to write to register 0. + RiscvRegId::Gpr(i) if (1..32).contains(&i) => { + self.state.init_register_unsafe(RegIdx::from(i), buf) + } + RiscvRegId::Pc => self.state.set_pc(ByteAddr(buf)), + // TODO(Matthias): see whether we can make this more specific. + _ => return Err(NonFatal), + } + Ok(()) + } +} + impl SingleThreadBase for MyTarget { + #[inline(always)] + fn support_single_register_access(&mut self) -> Option> { + Some(self) + } + fn read_registers( &mut self, - // regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, regs: &mut gdbstub_arch::riscv::reg::RiscvCoreRegs, ) -> TargetResult<(), Self> { for (i, reg) in enumerate(&mut regs.x) { - *reg = self.0.peek_register(i); + *reg = self.state.peek_register(i); } - regs.pc = u32::from(self.0.get_pc()); + regs.pc = u32::from(self.state.get_pc()); Ok(()) } @@ -59,18 +115,46 @@ impl SingleThreadBase for MyTarget { regs: &gdbstub_arch::riscv::reg::RiscvCoreRegs, ) -> TargetResult<(), Self> { for (i, reg) in enumerate(®s.x) { - self.0.init_register_unsafe(i, *reg); + self.state.init_register_unsafe(i, *reg); } - self.0.set_pc(ByteAddr::from(regs.pc)); + self.state.set_pc(ByteAddr::from(regs.pc)); Ok(()) } fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult { - todo!() + // TODO: deal with misaligned accesses + if !start_addr.is_multiple_of(4) { + return Err(NonFatal); + } + if !data.len().is_multiple_of(4) { + return Err(NonFatal); + } + let start_addr = WordAddr::from(ByteAddr(start_addr)); + + for (i, chunk) in enumerate(data.chunks_exact_mut(4)) { + let addr = start_addr + i * 4; + let word = self.state.peek_memory(addr); + chunk.copy_from_slice(&word.to_le_bytes()); + } + Ok(data.len()) } fn write_addrs(&mut self, start_addr: u32, data: &[u8]) -> TargetResult<(), Self> { - todo!() + // TODO: deal with misaligned accesses + if !start_addr.is_multiple_of(4) { + return Err(NonFatal); + } + if !data.len().is_multiple_of(4) { + return Err(NonFatal); + } + let start_addr = WordAddr::from(ByteAddr(start_addr)); + for (i, chunk) in enumerate(data.chunks_exact(4)) { + self.state.init_memory( + start_addr + i * 4, + u32::from_le_bytes(chunk.try_into().unwrap()), + ); + } + Ok(()) } // most targets will want to support at resumption as well... @@ -81,8 +165,10 @@ impl SingleThreadBase for MyTarget { } } +// TODO(Matthias): also support reverse stepping. impl SingleThreadResume for MyTarget { fn resume(&mut self, signal: Option) -> Result<(), Self::Error> { + // We need to step until the next breakpoint? todo!() } @@ -96,8 +182,10 @@ impl SingleThreadResume for MyTarget { } impl SingleThreadSingleStep for MyTarget { - fn step(&mut self, signal: Option) -> Result<(), Self::Error> { - todo!() + fn step(&mut self, _signal: Option) -> Result<(), Self::Error> { + // We might want to step with something higher level than rv32im::step, so we can go backwards in time? + crate::rv32im::step(&mut self.state)?; + Ok(()) } } diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index 3fda128bb..b4f030481 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::cargo)] #![feature(step_trait)] +#![feature(unsigned_is_multiple_of)] mod addr; pub use addr::*; From 184384773eda7763f616133693b5c7359ac5b94b Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 15:27:55 +0800 Subject: [PATCH 08/10] More implementation --- ceno_emul/src/gdb.rs | 42 ++++++++++++++++++++++++++------------- ceno_emul/src/vm_state.rs | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/ceno_emul/src/gdb.rs b/ceno_emul/src/gdb.rs index bc105cb50..761784877 100644 --- a/ceno_emul/src/gdb.rs +++ b/ceno_emul/src/gdb.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use gdbstub::{ - arch::{Arch, BreakpointKind}, + arch::Arch, common::Signal, target::{ Target, @@ -16,21 +16,19 @@ use gdbstub::{ SingleThreadSingleStep, SingleThreadSingleStepOps, }, }, - breakpoints::{ - Breakpoints, BreakpointsOps, HwBreakpointOps, SwBreakpoint, SwBreakpointOps, - }, + breakpoints::{Breakpoints, BreakpointsOps, SwBreakpoint, SwBreakpointOps}, }, }, }; use gdbstub_arch::riscv::{Riscv32, reg::id::RiscvRegId}; -use itertools::{Itertools, enumerate}; +use itertools::enumerate; use crate::{ByteAddr, EmuContext, RegIdx, VMState, WordAddr}; // This should probably reference / or be VMState? pub struct MyTarget { state: VMState, - break_points: BTreeSet<(u32, ::BreakpointKind)>, + break_points: BTreeSet, } impl Target for MyTarget { @@ -49,7 +47,6 @@ impl Target for MyTarget { } } -// TODO: add SingleRegisterAccess impl SingleRegisterAccess<()> for MyTarget { fn read_register( &mut self, @@ -167,9 +164,18 @@ impl SingleThreadBase for MyTarget { // TODO(Matthias): also support reverse stepping. impl SingleThreadResume for MyTarget { - fn resume(&mut self, signal: Option) -> Result<(), Self::Error> { - // We need to step until the next breakpoint? - todo!() + fn resume(&mut self, _signal: Option) -> Result<(), Self::Error> { + // TODO: iterate until either halt or breakpoint. + loop { + if self.state.halted() { + return Ok(()); + } + // TOOD: encountering an illegal instruction should NOT be a fatal error. + crate::rv32im::step(&mut self.state)?; + if self.break_points.contains(&u32::from(self.state.get_pc())) { + return Ok(()); + } + } } // ...and if the target supports resumption, it'll likely want to support @@ -202,16 +208,24 @@ impl SwBreakpoint for MyTarget { fn add_sw_breakpoint( &mut self, addr: u32, - kind: ::BreakpointKind, + _kind: ::BreakpointKind, ) -> TargetResult { - todo!() + // assert_eq!(kind, 0); + // TODO: consider always succeeding, and supporting multiple of the same breakpoint? + // What does gdb expect? + // At the moment we fail, if the breakpoint already exists. + Ok(self.break_points.insert(addr)) } fn remove_sw_breakpoint( &mut self, addr: u32, - kind: ::BreakpointKind, + _kind: ::BreakpointKind, ) -> TargetResult { - todo!() + // assert_eq!(kind, 0); + // TODO: consider always succeeding, and supporting multiple of the same breakpoint? + // What does gdb expect? + // At the moment we fail, if the breakpoint doesn't exist. + Ok(self.break_points.remove(&addr)) } } diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 49f22a747..745f3067a 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -21,7 +21,7 @@ pub struct VMState { memory: HashMap, registers: [Word; VMState::REG_COUNT], // Termination. - halted: bool, + pub halted: bool, tracer: Tracer, } From cc5d2f29ae2b37826422b24841a9fd30bc55cd17 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 3 Jan 2025 15:45:02 +0800 Subject: [PATCH 09/10] Minimise diff --- Cargo.toml | 2 +- ceno_emul/src/syscalls.rs | 2 +- ceno_rt/src/lib.rs | 5 ++++- ceno_rt/src/syscalls.rs | 12 +++++------- examples-builder/build.rs | 2 +- examples/examples/ceno_rt_keccak.rs | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0a5361732..04c31fd15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] +exclude = ["examples"] members = [ "ceno_emul", "ceno_host", "ceno_rt", "ceno_zkvm", "examples-builder", - "examples", "mpcs", "multilinear_extensions", "poseidon", diff --git a/ceno_emul/src/syscalls.rs b/ceno_emul/src/syscalls.rs index d5ca85402..251bedbae 100644 --- a/ceno_emul/src/syscalls.rs +++ b/ceno_emul/src/syscalls.rs @@ -6,7 +6,7 @@ pub mod keccak_permute; // Using the same function codes as sp1: // https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/core/executor/src/syscalls/code.rs -pub use ceno_rt::syscalls::KECCAK_PERMUTE; +pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; /// Trace the inputs and effects of a syscall. pub fn handle_syscall(vm: &VMState, function_code: u32) -> Result { diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index b48279d69..576615802 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -22,7 +22,10 @@ pub use io::info_out; mod params; pub use params::*; -pub mod syscalls; +#[cfg(target_arch = "riscv32")] +mod syscalls; +#[cfg(target_arch = "riscv32")] +pub use syscalls::*; #[no_mangle] #[linkage = "weak"] diff --git a/ceno_rt/src/syscalls.rs b/ceno_rt/src/syscalls.rs index 1ebef70bc..90ace85da 100644 --- a/ceno_rt/src/syscalls.rs +++ b/ceno_rt/src/syscalls.rs @@ -1,8 +1,8 @@ // Based on https://github.com/succinctlabs/sp1/blob/013c24ea2fa15a0e7ed94f7d11a7ada4baa39ab9/crates/zkvm/entrypoint/src/syscalls/keccak_permute.rs -#[cfg(target_os = "zkvm")] -use core::arch::asm; -pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; +const KECCAK_PERMUTE: u32 = 0x00_01_01_09; + +use core::arch::asm; /// Executes the Keccak256 permutation on the given state. /// @@ -11,8 +11,8 @@ pub const KECCAK_PERMUTE: u32 = 0x00_01_01_09; /// The caller must ensure that `state` is valid pointer to data that is aligned along a four /// byte boundary. #[allow(unused_variables)] -pub fn keccak_permute(state: &mut [u64; 25]) { - #[cfg(target_os = "zkvm")] +#[no_mangle] +pub extern "C" fn syscall_keccak_permute(state: &mut [u64; 25]) { unsafe { asm!( "ecall", @@ -21,6 +21,4 @@ pub fn keccak_permute(state: &mut [u64; 25]) { in("a1") 0 ); } - #[cfg(not(target_os = "zkvm"))] - unreachable!() } diff --git a/examples-builder/build.rs b/examples-builder/build.rs index ba3c6e6df..b6dee0cfe 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -56,7 +56,7 @@ fn build_elfs() { dest, r#"#[allow(non_upper_case_globals)] pub const {example}: &[u8] = - include_bytes!(r"{CARGO_MANIFEST_DIR}/../target/riscv32im-ceno-zkvm-elf/release/examples/{example}");"# + include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-ceno-zkvm-elf/release/examples/{example}");"# ).expect("failed to write vars.rs"); } rerun_all_but_target(Path::new("../examples")); diff --git a/examples/examples/ceno_rt_keccak.rs b/examples/examples/ceno_rt_keccak.rs index 57d9f76ed..7a01557bf 100644 --- a/examples/examples/ceno_rt_keccak.rs +++ b/examples/examples/ceno_rt_keccak.rs @@ -3,7 +3,7 @@ //! Iterate multiple times and log the state after each iteration. extern crate ceno_rt; -use ceno_rt::{info_out, syscalls::keccak_permute}; +use ceno_rt::{info_out, syscall_keccak_permute}; use core::slice; const ITERATIONS: usize = 3; @@ -12,7 +12,7 @@ fn main() { let mut state = [0_u64; 25]; for _ in 0..ITERATIONS { - keccak_permute(&mut state); + syscall_keccak_permute(&mut state); log_state(&state); } } From 9b6573e2ab9638b255c470d59c09159fa964276e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Mon, 6 Jan 2025 11:08:55 +0800 Subject: [PATCH 10/10] Remove ceno_rt here --- Cargo.lock | 1 - ceno_emul/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b17bec6e0..dec8e8a7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,6 @@ name = "ceno_emul" version = "0.1.0" dependencies = [ "anyhow", - "ceno_rt", "elf", "gdbstub", "gdbstub_arch", diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 611dbfd7c..efc71329c 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -11,7 +11,6 @@ version.workspace = true [dependencies] anyhow.workspace = true -ceno_rt = { path = "../ceno_rt" } elf = "0.7" gdbstub = "0.7.3" gdbstub_arch = "0.3.1"