rigless 0.2.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The emulator, and the seam between it and the models.
//!
//! The emulator executes instructions, and the models answer the peripheral accesses those
//! instructions make. The emulator is the target's core, its family's [`Processor`], and [`run`]
//! runs it on a thread of its own: every access is a call into [`Session::access`] on that thread, a debugger's
//! among them, and every instant the models asked to be told about is a call into
//! [`Session::stop`]. Nothing crosses a process boundary, and nothing waits on another thread.
//!
//! What the emulator lends the models while it calls them is a [`Host`]: guest memory, which a
//! model moving a block reads and writes, the interrupt lines, which a model raises and lowers, and
//! the emulator's own state, which a snapshot keeps, and watches on an instruction or a range of
//! memory, which a trigger counts. What the process lends the run is a [`Driver`]: it sees every
//! access, every hit on what the emulator watches and every stop once the models have settled, and at every
//! instant the run was asked to reach it says where to reach next, or that the run is over. A run
//! started from a snapshot [`Resume`]s at the stop the snapshot was taken at, with the emulator's
//! state and memory put back before its first instruction. `rigless run`'s driver prints the
//! accesses and reads the outputs. `rigless run --link`'s answers the engine on the far side of the
//! link with the outputs and takes the next instant from it.
//!
//! A machine module says what its target's core is made as in a [`CoreSpec`]: the function
//! that makes its [`Processor`], holding the family's own typed configuration, the instruction
//! rate, the regions a second address also reaches, and where the core boots. The runtime adds the
//! images, the memory, the address a run boots from and the debugger, and never sees the family's
//! configuration. A family's core meets the runner through [`Processor`] and the [`CoreBus`] it's
//! lent, so a family outside this crate plugs in the same way the one inside it does.
//!
//! [`Session::access`]: crate::session::Session::access
//! [`Session::stop`]: crate::session::Session::stop

use std::collections::BTreeMap;
use std::fmt;
use std::net::TcpListener;
use std::num::NonZeroU64;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;

use crate::device::Level;
use crate::image::Image;
use crate::machine::Board;
use crate::session::Session;
use crate::soc::{Endian, Width};
use crate::time::VirtualNanos;

#[cfg(not(emulator_absent))]
pub(crate) mod core;
#[cfg_attr(
    all(emulator_absent, not(test)),
    expect(
        dead_code,
        reason = "only the core uses it, and Miri builds the crate without one"
    )
)]
pub(crate) mod memory;
mod printed;
mod processor;
#[cfg(not(emulator_absent))]
mod runner;

pub use printed::{Accesses, NO_DEADLINE, Printed, run_printed};
pub use processor::{BitAlias, CoreBus, Exit, HaltReason, Processor, Queued};
#[cfg(all(fuzzing, not(emulator_absent)))]
pub(crate) use runner::Fuzzed;
#[cfg(not(emulator_absent))]
pub use runner::run;

/// Runs a firmware on a session's target, and answers [`Error::NoEmulator`] because this build has
/// no emulator to run it on.
///
/// Only Miri reaches this: it interprets Rust and can't execute the code the core generates, and it
/// still has to produce a crate. Removing `run` from that build instead would leave the function
/// the rest of this module is about out of what it checks.
#[cfg(emulator_absent)]
pub fn run<D, R>(
    _session: Session<D>,
    _start: &Start<'_>,
    _driver: R,
) -> Result<Finished<D, R>, Error>
where
    D: Board + ?Sized + 'static,
    R: Driver<D> + 'static,
{
    Err(Error::NoEmulator)
}

// ceiling: a line a part drives is below this; upgrade when a core family has more lines.
/// One past the highest interrupt line the emulator drives.
pub const INTERRUPT_LINES: u16 = 1024;

/// An interrupt line the emulator drives, so an index below [`INTERRUPT_LINES`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InterruptLine(u16);

impl InterruptLine {
    /// The line at `index`, or `None` when no core family has one there.
    #[must_use]
    pub const fn new(index: u16) -> Option<Self> {
        if index < INTERRUPT_LINES {
            Some(Self(index))
        } else {
            None
        }
    }

    /// The index this line is.
    #[must_use]
    pub const fn index(self) -> u16 {
        self.0
    }
}

/// How a core family's C compilers lay its data out: the width of a pointer and of a `long`. A
/// probe reading the firmware's structures, a debugger's `printf`, a host call's block and how wide a
/// debugger's access goes all read it from [`Processor::data_model`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DataModel {
    /// A 32-bit `int`, `long` and pointer.
    Ilp32,
    /// A 32-bit `int`, and a 64-bit `long` and pointer.
    Lp64,
}

impl DataModel {
    /// The width of a pointer.
    #[must_use]
    pub const fn pointer(self) -> Width {
        match self {
            Self::Ilp32 => Width::W32,
            Self::Lp64 => Width::W64,
        }
    }

    /// The width of a `long`.
    #[must_use]
    pub const fn long(self) -> Width {
        match self {
            Self::Ilp32 => Width::W32,
            Self::Lp64 => Width::W64,
        }
    }
}

/// Whether an access reads or writes.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum AccessKind {
    /// A read.
    Read,
    /// A write.
    Write,
}

/// One peripheral access as the emulated core made it.
///
/// [`Access::new`] makes a debugger's, at no instant, and [`Access::at`] and
/// [`Access::by_instruction`] say when and by what the core made it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Access {
    /// Read or write.
    pub kind: AccessKind,
    /// The address on the bus.
    pub address: u64,
    /// The width the core asked for.
    pub width: Width,
    /// The value written, or zero for a read.
    pub value: u64,
    /// The emulator's instruction count at the access.
    pub instruction_count: u64,
    /// The virtual clock at the access.
    pub virtual_time: VirtualNanos,
    /// The instruction making the access, or `None` for a debugger's, which no instruction made.
    pub program_counter: Option<u64>,
}

impl Access {
    /// An access of `kind` and `width` at `address`, carrying `value` for a write and zero for a
    /// read, at instruction count zero and time zero, with no instruction making it.
    #[must_use]
    pub const fn new(kind: AccessKind, address: u64, width: Width, value: u64) -> Self {
        Self {
            kind,
            address,
            width,
            value,
            instruction_count: 0,
            virtual_time: VirtualNanos(0),
            program_counter: None,
        }
    }

    /// This access, made at `instruction_count` instructions and `virtual_time`.
    #[must_use]
    pub const fn at(self, instruction_count: u64, virtual_time: VirtualNanos) -> Self {
        Self {
            instruction_count,
            virtual_time,
            ..self
        }
    }

    /// This access, made by the instruction at `program_counter`.
    #[must_use]
    pub const fn by_instruction(self, program_counter: u64) -> Self {
        Self {
            program_counter: Some(program_counter),
            ..self
        }
    }
}

/// Why the emulator stopped.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum StopReason {
    /// The instant the models asked for came.
    Deadline,
    /// The core was halted when that instant came, so the clock warped to it.
    Idle,
    /// The firmware requested a system reset.
    Reset,
}

/// Where and why the emulator stopped.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Stop {
    /// The virtual clock at the stop.
    pub at: VirtualNanos,
    /// The emulator's instruction count at the stop.
    pub instruction_count: u64,
    /// Why.
    pub reason: StopReason,
}

/// Guest memory, which a model reaches only through the emulator.
///
/// A model that must move a word to or from RAM, a DMA controller, asks for it here, at the instant
/// of the access or the event that caused it. The emulator serves it from its own address space,
/// and a test serves it from a map.
pub trait Memory {
    /// The `width` at `address`.
    fn read(&mut self, address: u64, width: Width) -> Result<u64, Error>;

    /// Writes `value` as `width` at `address`.
    fn write(&mut self, address: u64, width: Width, value: u64) -> Result<(), Error>;

    /// The order of a value's bytes in this memory, which [`Memory::read`] and [`Memory::write`]
    /// keep. Little-endian unless the memory says otherwise.
    fn endian(&self) -> Endian {
        Endian::Little
    }
}

/// What the emulator lends the models while it calls them: its memory, its interrupt lines, a
/// system reset, and its own state.
pub trait Host: Memory {
    /// Drives `line` to `level` before the next instruction executes.
    fn set_interrupt(&mut self, line: InterruptLine, level: Level);

    /// Restarts the emulator's half of the target from its reset vector, after the runtime reset the models, with no instruction between.
    fn request_reset(&mut self);

    /// The state of everything the emulator holds but memory, the core, its interrupt controller,
    /// its timers and the clock, as it stands at a stop. A host that holds none answers that it
    /// can't.
    fn save_state(&mut self) -> Result<Vec<u8>, Error> {
        Err(Error::State)
    }

    /// Starts watching `watch`, and answers whether it could. Each hit reaches the driver as
    /// [`Settled::Hit`]. A host that can't watch anything answers that it couldn't. A watch on an
    /// instruction asked while the core runs answers that it could, and the core hears of it once
    /// it stops: if it can't stop there, the run fails with [`Error::Unwatched`] then.
    fn watch(&mut self, watch: Watch) -> bool {
        let _ = watch;
        false
    }

    /// Stops watching `watch`.
    fn unwatch(&mut self, watch: Watch) {
        let _ = watch;
    }
}

/// A family's constructor: the processor its machine module configured, for a part that keeps its
/// data in the order given, or why the family can't make it. The family's module captures its own
/// typed configuration in it, so the runtime never reads one.
pub type ProcessorConstructor =
    Arc<dyn Fn(Endian) -> Result<Box<dyn Processor>, Error> + Send + Sync>;

/// What one target's core is made as, as its machine module renders it.
///
/// [`CoreSpec::new`] makes one with no aliases, and each field is yours to set after.
#[derive(Clone)]
#[non_exhaustive]
pub struct CoreSpec {
    /// Makes the target's processor. The family's module puts its own here, configured, and
    /// [`run`] calls it, so nothing in the runtime names a family.
    pub processor: ProcessorConstructor,
    /// How many instructions the core retires in a second of virtual time. The machine module owns
    /// the rate. An instruction takes a billion over this many nanoseconds, and the clock is the
    /// whole nanoseconds every instruction retired so far takes, rounded down, so the run's instants
    /// are the same on every host.
    pub instructions_per_second: NonZeroU64,
    /// The regions a second address also reaches, which is the board's wiring and not the core's business.
    pub aliases: Vec<Alias>,
    /// Where the core boots from, and how a run moves it.
    pub boot: Boot,
    /// The machine of the ELF files the core runs, or `None` for a core that takes any. Placing an
    /// image refuses an ELF file built for another, and one in the other byte order from the part's
    /// whatever this says.
    pub elf: Option<crate::elf::Machine>,
    /// Where the first instruction of a function is, from the value its ELF symbol has, which a
    /// trigger on a function's name and a debugger's look-up of one read through. It's the value as
    /// it is unless the family's ABI keeps something else there.
    pub code_address: fn(u64) -> u64,
}

impl CoreSpec {
    /// The core `processor` makes, retiring `instructions_per_second` and booting as `boot`, with
    /// no aliases, taking an ELF file built for any machine.
    #[must_use]
    pub fn new(
        processor: ProcessorConstructor,
        instructions_per_second: NonZeroU64,
        boot: Boot,
    ) -> Self {
        Self {
            processor,
            instructions_per_second,
            aliases: Vec::new(),
            boot,
            elf: None,
            code_address: std::convert::identity,
        }
    }
}

impl fmt::Debug for CoreSpec {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("CoreSpec")
            .field("instructions_per_second", &self.instructions_per_second)
            .field("aliases", &self.aliases)
            .field("boot", &self.boot)
            .field("elf", &self.elf)
            .finish_non_exhaustive()
    }
}

/// A second address the whole of a memory region also answers at.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Alias {
    /// The region's name in the target's memory map. A name the map does not have is refused when the
    /// session opens.
    pub region: &'static str,
    /// Where the same bytes appear again.
    pub at: u64,
}

/// Where a core boots from, and what a run that moves it has to keep to.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Boot {
    /// Where the core boots from unless the run names another address.
    pub reset: u64,
    /// What an address the core boots from has to be a multiple of.
    pub alignment: NonZeroU64,
}

impl Boot {
    /// A core that boots from `reset`, and from any other multiple of `alignment` a run names.
    #[must_use]
    pub const fn new(reset: u64, alignment: NonZeroU64) -> Self {
        Self { reset, alignment }
    }
}

/// How one run starts: the images in memory, where the core boots from, whether a debugger is served
/// and the kernels whose threads it shows, what the firmware may ask of the host, and the snapshot it
/// resumes from, if any.
///
/// [`Start::new`] makes one with only the images, and each field is yours to set after.
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Start<'a> {
    /// The images the emulator places before the core's first instruction.
    pub images: &'a [Image],
    /// Where the core boots from, or `None` for where it boots on its own.
    pub boot: Option<u64>,
    /// The listener to serve the debugger on, if any, bound wherever you want the debugger to
    /// connect. The run takes debuggers from it until it ends and leaves it non-blocking. One
    /// listener serves one run, so bind a new one for the next.
    pub debugger: Option<&'a TcpListener>,
    /// Hold the core before its first instruction until a debugger continues, steps, detaches or
    /// kills it. One that closes before then leaves the core held for the next.
    pub wait_for_debugger: bool,
    /// Where a run started from a snapshot picks up: what [`crate::snapshot::Snapshot::restore`]
    /// answered once it loaded the session, which must then be asked to reach its next instant.
    pub resume: Option<&'a Resume<'a>>,
    /// Serve the firmware's host calls, [`HostCall`]: its console reaches
    /// [`Driver::on_console`], its clock reads virtual time, its exit ends the run, and its file
    /// calls go to an attached debugger's host, or fail with no debugger. Without it a host call
    /// is the instruction it is on a part with nothing serving it.
    pub semihosting: bool,
    /// The listener to stream the processor's own trace to, if any, in the trace protocol of the
    /// processor's family, one client at a time. With no client the trace goes, and the run never
    /// waits on one.
    pub trace: Option<&'a TcpListener>,
    /// The listener to stream the firmware's RTT terminal to, if any, one client at a time. The
    /// run drains it every virtual millisecond, at whole milliseconds, writing its read offset back
    /// as a probe does, which is what makes room for more, so a firmware that waits for room runs
    /// as it would with a probe attached. Without it nothing reads the terminal.
    pub rtt: Option<&'a TcpListener>,
    /// The kernels whose threads the debugger shows, the first whose required symbols GDB found
    /// winning. With none, the core is the one thread.
    pub kernels: &'a [Box<dyn WalkThreads>],
    /// A flag that ends the run once it's set, between two instructions with every stop due by
    /// then taken, and while a debugger holds the core too. [`Finished::interrupted`] says where.
    /// `rigless run` sets it at Ctrl-C.
    pub interrupt: Option<&'a AtomicBool>,
}

impl<'a> Start<'a> {
    /// A run of `images` from where the core boots, with no debugger, host calls, kernels or
    /// snapshot.
    #[must_use]
    pub fn new(images: &'a [Image]) -> Self {
        Self {
            images,
            boot: None,
            debugger: None,
            wait_for_debugger: false,
            resume: None,
            semihosting: false,
            trace: None,
            rtt: None,
            kernels: &[],
            interrupt: None,
        }
    }
}

/// The emulator's half of a snapshot, which a run started from one puts back before its first
/// instruction: the stop it was taken at, the emulator's own state there, and every writable
/// region's bytes at the address it starts at. Only [`crate::snapshot::Snapshot::restore`] makes one,
/// once it has checked it against the session.
///
/// The emulator stops at that same instant before anything runs, and the session carries on from
/// there as it did after the stop the snapshot was taken inside. It doesn't stop a second time,
/// since the run that took the snapshot already had.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Resume<'a> {
    pub(crate) stop: Stop,
    pub(crate) state: &'a [u8],
    pub(crate) memory: Vec<(u64, &'a [u8])>,
}

/// Something the emulator watches for the models' side, apart from a debugger's own breakpoints.
#[derive(
    Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[non_exhaustive]
pub enum Watch {
    /// The instruction at this address, hit each time the core arrives at it, before it runs.
    Instruction(u64),
    /// Every instruction that stores into `length` bytes from `address`, hit once its store has
    /// landed. A store the core makes on its own, entering an exception say, is no instruction's
    /// and no hit.
    Writes {
        /// The first byte.
        address: u64,
        /// How many.
        length: u64,
    },
}

/// A hit on something the emulator watches.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Hit {
    /// An arrival at an instruction, or a store that has landed.
    pub kind: HitKind,
    /// The instruction's address, or the address written.
    pub address: u64,
    /// The virtual clock at the hit.
    pub at: VirtualNanos,
    /// The emulator's instruction count at the hit.
    pub instruction_count: u64,
}

/// What a watch was hit by.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HitKind {
    /// The core arrived at an instruction to run it.
    Instruction,
    /// An instruction's store has landed.
    Write,
}

/// Where a run stands once the models have settled: right after an access, at a stop, or at a hit on
/// something the emulator watches.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Settled {
    /// An access, with the value read or written.
    Access {
        /// The access as the core made it.
        access: Access,
        /// The value read, or the value written back.
        value: u64,
    },
    /// A stop, whether or not it's the instant the run was asked to reach.
    Stop(Stop),
    /// A hit on something the emulator watches, between two instructions.
    Hit(Hit),
}

/// What the process does with a run while the emulator drives it.
///
/// Both methods run inside the emulator's call, on the core's thread, before the next instruction
/// executes. Nothing else touches the session while they do. A stop has the run between two instructions with every
/// model settled, so it's where [`crate::snapshot::take`] takes a snapshot, with the host `on_stop`
/// is lent.
pub trait Driver<D: Board + ?Sized>: Send {
    /// Every access, every hit on what the emulator watches and every stop, once the models have
    /// settled after it. What a driver changes
    /// here, the run carries on from: a board condition set, or an instant to pause at asked for with
    /// [`Session::pause_at`]. A run started from a snapshot has it once more at the stop it resumes
    /// at, for whatever the driver was handed before the first instruction.
    fn on_settled(&mut self, session: &mut Session<D>, host: &mut dyn Host, settled: Settled) {
        let _ = (session, host, settled);
    }

    /// The run reached the instant it was asked to: the next instant to reach, or `None` to end the run.
    /// The run resumes on its own once this answers, with [`Session::resume`], so whatever the driver
    /// handed the board here reaches the models at this same stop. A driver never calls it itself.
    fn on_stop(
        &mut self,
        session: &mut Session<D>,
        stop: Stop,
        host: &mut dyn Host,
    ) -> Option<VirtualNanos>;

    /// Bytes the firmware wrote to the host's console at `at`, after `instruction_count`
    /// instructions, with [`Start::semihosting`] on.
    fn on_console(&mut self, at: VirtualNanos, instruction_count: u64, bytes: &[u8]) {
        let _ = (at, instruction_count, bytes);
    }

    /// What a debugger's breakpoint printed at `at`, after `instruction_count` instructions, in
    /// non-stop mode, where GDB shows none of it: a `dprintf`'s text, or why its condition couldn't
    /// be worked out. You hear each once, however often a debugger takes the run back over it, and
    /// it's no call that going back counts.
    fn on_debugger_print(&mut self, at: VirtualNanos, instruction_count: u64, text: &[u8]) {
        let _ = (at, instruction_count, text);
    }

    /// A debugger took the core at `at`, after `instruction_count` instructions, or with `attached`
    /// false let it go or went. While one has it the core's halting debug is on, which changes
    /// what a breakpoint instruction does, so this says where the run was changed so.
    fn on_debugger_attached(&mut self, at: VirtualNanos, instruction_count: u64, attached: bool) {
        let _ = (at, instruction_count, attached);
    }

    /// Whether a debugger may take the run back. Say so only when your calls change nothing in the
    /// run or the session but which instant `on_stop` answers, no watch, pause or condition among
    /// them: while the run goes forward again over what it already did, you're called for none of
    /// it, so a watch you'd have set there is never set, and `on_stop`'s answers are the ones you
    /// gave the first time.
    fn may_rewind(&self) -> bool {
        false
    }
}

/// What a run leaves behind: the session with its reports, the driver, and the stop that ended it.
#[derive(Debug)]
#[non_exhaustive]
pub struct Finished<D: Board + ?Sized, R> {
    /// The session, its board and its reports as the run left them.
    pub session: Session<D>,
    /// The driver, with whatever it kept.
    pub driver: R,
    /// The stop the driver ended the run at, or `None` when something else ended it.
    pub stop: Option<Stop>,
    /// The status the firmware ended the run with, through a host call, or `None` when it didn't.
    pub exit_status: Option<u32>,
    /// Where the run stood when its [`Start::interrupt`] ended it, or `None` when it didn't.
    pub interrupted: Option<Interrupted>,
}

/// Where a run stood when its [`Start::interrupt`] ended it: between two instructions, with every
/// stop due by then taken. The models never hear of it, so it's no [`Stop`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Interrupted {
    /// The virtual clock then.
    pub at: VirtualNanos,
    /// The emulator's instruction count then.
    pub instruction_count: u64,
}

/// What can go wrong between the emulator and the models.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
    /// The board has no target at the index asked for.
    #[error("{machine} has no target {target}: it has {count}")]
    NoTarget {
        /// The board.
        machine: &'static str,
        /// The index asked for.
        target: usize,
        /// How many the board has.
        count: usize,
    },
    /// The machine puts a region at a second address, and the target's memory map has no region by that name.
    #[error("{machine} puts a region called {region} at {at:#x}, but {target} has no such region (it has {})", regions.join(", "))]
    NoRegion {
        /// The board.
        machine: &'static str,
        /// The target.
        target: &'static str,
        /// The name the machine asked for.
        region: &'static str,
        /// The second address it asked for it at.
        at: u64,
        /// The names the target's memory map has.
        regions: Vec<&'static str>,
    },
    /// The machine puts a region at a second address that a region, an instance, a window or
    /// another alias already covers, so the core would answer there from either.
    #[error("{machine} puts {region} at {at:#x}, where {target} already has {other}")]
    OverlappingAlias {
        /// The board.
        machine: &'static str,
        /// The target.
        target: &'static str,
        /// The region the machine asked for again.
        region: &'static str,
        /// The second address it asked for it at.
        at: u64,
        /// The region already there, or the one an alias already puts there.
        other: &'static str,
    },
    /// Guest memory refused an access a model asked for.
    #[error("guest memory has nothing at {address:#x} for a {} bit access", width.bits())]
    Memory {
        /// The address.
        address: u64,
        /// The width.
        width: Width,
    },
    /// The debugger couldn't be served on the listener it was given.
    #[error("serving the debugger: {0}")]
    Debugger(std::io::Error),
    /// This build has no emulator.
    #[error("this build has no emulator, and only Miri builds the crate without one")]
    NoEmulator,
    /// A printed run could not write where it was printing.
    #[error("writing the run's output: {0}")]
    Output(#[from] std::io::Error),
    /// A printed run was asked to read the outputs every 0 ns, which would never move the run on.
    #[error("a period of 0 ns would read the outputs forever at one instant")]
    ZeroPeriod,
    /// The emulator couldn't start watching what a driver asked it to.
    #[error("the emulator couldn't watch {0:?}")]
    Unwatched(Watch),
    /// The images couldn't be placed as the run asked.
    #[error(transparent)]
    Image(#[from] crate::image::Error),
    /// The board's edge declares what the board doesn't carry: a field, a type or a chip.
    #[error(transparent)]
    Edge(#[from] crate::edge::Mismatch),
    /// A printed run was asked to read the outputs of a board that has none.
    #[error(
        "{machine} has no edge, so there are no outputs to read. Implement `Edge` for it and have `Board::edge` answer it"
    )]
    NoEdge {
        /// The board.
        machine: &'static str,
    },
    /// The core can't run the target as its machine module describes it.
    #[error("{0}")]
    Machine(String),
    /// The emulator couldn't save or load its own state: it holds none, or the state isn't one this
    /// build saved.
    #[error(
        "the emulator couldn't save or load its own state: it holds none, or the state isn't one this build saved"
    )]
    State,
    /// A debugger wrote memory the guest can't write, which a snapshot keeps only as the images
    /// placed it.
    #[error(
        "a debugger wrote memory the guest can't write, which a snapshot keeps only as the images placed it, so the run takes no snapshot"
    )]
    WroteReadOnly,
    /// An output port couldn't be served on the listener it was given.
    #[error("serving an output port: {0}")]
    OutputPort(std::io::Error),
}

/// What a firmware asked of the host it runs on, which [`Processor::host_call`] decodes from where
/// the core stopped with [`Exit::HostCall`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum HostCall {
    /// Write `length` bytes from `address` to `handle`, where 1 and 2 are the console.
    Write {
        /// The file, or the console.
        handle: u64,
        /// Where the bytes are.
        address: u64,
        /// How many there are.
        length: u64,
    },
    /// End the run with `code`.
    Exit {
        /// What the firmware ends with, as a program's exit status.
        code: u32,
    },
    /// Hundredths of a second of virtual time since the run began.
    Clock,
    /// Seconds since 1970, taking the run's start as 1970 and counting virtual time.
    Time,
    /// Virtual nanoseconds since the run began, which go in the two 32-bit words at `block`, the low
    /// one first, in the target's byte order.
    Elapsed {
        /// Where the two words are.
        block: u64,
    },
    /// How many of [`HostCall::Elapsed`]'s ticks make a second.
    TickFrequency,
    /// Open the file named by `length` bytes at `path`.
    Open {
        /// Where the name is.
        path: u64,
        /// How long it is, with no terminating NUL.
        length: u64,
        /// What the file's opened for.
        mode: OpenMode,
    },
    /// Open the console, which is `handle`: 0 to read it and 1 to write it.
    OpenConsole {
        /// The handle the console has.
        handle: u64,
    },
    /// Close `handle`.
    Close {
        /// The file.
        handle: u64,
    },
    /// Read up to `length` bytes from `handle` into `address`.
    Read {
        /// The file, or 0 for the console.
        handle: u64,
        /// Where the bytes go.
        address: u64,
        /// How many fit.
        length: u64,
    },
    /// Move `handle` to `position` bytes from its start.
    Seek {
        /// The file.
        handle: u64,
        /// Where to.
        position: u64,
    },
    /// The length of the file `handle` is.
    Length {
        /// The file.
        handle: u64,
    },
    /// Whether `handle` is the console.
    IsTty {
        /// The file, or the console.
        handle: u64,
    },
    /// The error number of the last call that failed.
    Errno,
}

/// How a file is opened: C's `r`, `r+`, `w`, `w+`, `a` and `a+`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum OpenMode {
    /// `r`: read it.
    Read,
    /// `r+`: read and write it.
    ReadWrite,
    /// `w`: write it from empty, making it if there's none.
    Write,
    /// `w+`: read and write it from empty, making it if there's none.
    WriteRead,
    /// `a`: write at its end, making it if there's none.
    Append,
    /// `a+`: read it and write at its end, making it if there's none.
    AppendRead,
}

/// Walks a firmware kernel's threads for the debugger, which shows GDB each one.
///
/// The server asks GDB to look up every kernel's [`WalkThreads::symbols`] through `qSymbol`, in the
/// order [`Start::kernels`] lists the kernels, and walks the first whose required symbols GDB found,
/// once a stop, when something first asks.
pub trait WalkThreads: fmt::Debug + Sync {
    /// The symbols the walk asks GDB to look up.
    fn symbols(&self) -> &[KernelSymbol];

    /// Every thread, or `None` when the kernel isn't running yet or its memory doesn't check out.
    /// The thread that runs is the one with nothing saved. A walk shows GDB the core as the one
    /// thread unless exactly one thread has nothing saved, no two share an id, and only the thread
    /// that runs has id 1, which GDB's `p1.1` names it by.
    ///
    /// `found` is the address GDB found for each of [`WalkThreads::symbols`], in their order.
    /// `registers` are the core's as it stopped, by the names its target description gives
    /// them, each as its bytes in `endian`, the target's order. `read` peeks at memory as the
    /// debugger's reads do, so the walk changes nothing.
    fn walk(
        &self,
        found: &[Option<u64>],
        registers: &BTreeMap<&str, Vec<u8>>,
        endian: Endian,
        read: &mut dyn FnMut(u64, Width) -> Option<u64>,
    ) -> Option<Vec<KernelThread>>;
}

/// A symbol a walk asks GDB to look up.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KernelSymbol {
    /// Its name in the firmware's symbol table.
    pub name: &'static str,
    /// Whether the walk needs it.
    pub required: bool,
}

/// One of a kernel's threads at a stop.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KernelThread {
    /// Its id, which GDB knows it by and which stays put while it lives. No other thread of the
    /// walk has it, and only the thread that runs may have 1, the id `p1.1` names it by.
    pub id: NonZeroU64,
    /// Its name, or `None` when it has none.
    pub name: Option<String>,
    /// What it's doing, in its kernel's words.
    pub state: &'static str,
    /// The registers its kernel saved when it switched away from it, by the names the target
    /// description gives them, each as its bytes in the target's order, or `None` for the thread
    /// that runs, whose registers are the core's. GDB reads a register the save doesn't keep, or
    /// keeps at another width, as unavailable.
    pub saved: Option<BTreeMap<&'static str, Vec<u8>>>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_line_exists_below_the_ceiling_and_not_at_it() {
        assert_eq!(InterruptLine::new(0).map(InterruptLine::index), Some(0));
        assert!(InterruptLine::new(INTERRUPT_LINES - 1).is_some());
        assert!(InterruptLine::new(INTERRUPT_LINES).is_none());
    }
}