probe_rs/
semihosting.rs

1//! ARM semihosting support.
2//!
3//! Specification: <https://github.com/ARM-software/abi-aa/blob/2024Q3/semihosting/semihosting.rst>
4
5use std::{num::NonZeroU32, time::SystemTime};
6
7use crate::{CoreInterface, Error, MemoryInterface, RegisterValue};
8
9/// Indicates the operation the target would like the debugger to perform.
10#[derive(Debug, PartialEq, Eq, Clone, Copy)]
11pub enum SemihostingCommand {
12    /// The target indicates that it completed successfully and no-longer wishes
13    /// to run.
14    ExitSuccess,
15
16    /// The target indicates that it completed unsuccessfully, with an error
17    /// code, and no-longer wishes to run.
18    ExitError(ExitErrorDetails),
19
20    /// The target indicates that it would like to read the command line arguments.
21    GetCommandLine(GetCommandLineRequest),
22
23    /// The target requests to open a file on the host.
24    Open(OpenRequest),
25
26    /// The target requests to close a file on the host.
27    Close(CloseRequest),
28
29    /// The target indicated that it would like to write to the console.
30    WriteConsole(WriteConsoleRequest),
31
32    /// The target indicated that it would like to write to a file on the host.
33    Write(WriteRequest),
34
35    /// The target indicated that it would like to read from a file on the host.
36    Read(ReadRequest),
37
38    /// The target indicated that it would like to seek in a file on the host.
39    Seek(SeekRequest),
40
41    /// The target indicated that it would like to read the length of a file on the host.
42    FileLength(FileLengthRequest),
43
44    /// The target indicated that it would like to remove a file on the host.
45    Remove(RemoveRequest),
46
47    /// The target indicated that it would like to rename a file on the host.
48    Rename(RenameRequest),
49
50    /// The target indicated that it would like to read the current time.
51    Time(TimeRequest),
52
53    /// The target indicated that it would like to read the value of errno.
54    Errno(ErrnoRequest),
55
56    /// The target indicated that it would like to run a semihosting operation which we don't support yet.
57    Unknown(UnknownCommandDetails),
58}
59
60/// Details of a semihosting exit with error
61#[derive(Debug, PartialEq, Eq, Copy, Clone)]
62pub struct ExitErrorDetails {
63    /// Some application specific exit reason:
64    /// <https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#651entry-32-bit>
65    pub reason: u32,
66
67    /// The exit status of the application, if present (only if reason == `ADP_Stopped_ApplicationExit` `0x20026`).
68    /// This is an exit status code, as passed to the C standard library exit() function.
69    pub exit_status: Option<u32>,
70
71    /// The subcode of the exit, if present (only if reason != `ADP_Stopped_ApplicationExit` `0x20026`).
72    pub subcode: Option<u32>,
73}
74
75impl std::fmt::Display for ExitErrorDetails {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        // <https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#651entry-32-bit>
78        const REASON_APPLICATION_EXIT: u32 = 0x20026;
79
80        // exit() codes
81        const EXIT_SUCCESS: u32 = 0;
82        const EXIT_ABORTED: u32 = 134;
83
84        match self.reason {
85            REASON_APPLICATION_EXIT => match self.exit_status {
86                Some(EXIT_SUCCESS) => write!(f, "success")?,
87                Some(EXIT_ABORTED) => write!(f, "exit_status: aborted")?,
88                Some(other) => write!(f, "exit_status: {other:#x}")?,
89                None => write!(f, "exit_status: unknown")?,
90            },
91            reason => {
92                write!(f, "reason: {reason:#x}")?;
93                if let Some(exit_status) = self.exit_status {
94                    write!(f, ", exit_status: {exit_status}")?;
95                }
96            }
97        }
98
99        if let Some(subcode) = self.subcode {
100            write!(f, ", subcode: {subcode:#x}")?;
101        }
102        Ok(())
103    }
104}
105
106/// Details of a semihosting operation that we don't support yet
107#[derive(Debug, PartialEq, Eq, Copy, Clone)]
108pub struct UnknownCommandDetails {
109    /// The semihosting operation requested
110    pub operation: u32,
111
112    /// The parameter to the semihosting operation
113    pub parameter: u32,
114}
115
116impl UnknownCommandDetails {
117    /// Returns the buffer pointed-to by the parameter of the semihosting operation
118    pub fn get_buffer(&self, core: &mut dyn CoreInterface) -> Result<Buffer, Error> {
119        Buffer::from_block_at(core, self.parameter)
120    }
121
122    /// Writes the status of the semihosting operation to the return register of the target
123    pub fn write_status(&self, core: &mut dyn CoreInterface, status: i32) -> Result<(), Error> {
124        write_status(core, status)
125    }
126}
127
128/// A request to read the command line arguments from the target
129#[derive(Debug, PartialEq, Eq, Copy, Clone)]
130pub struct GetCommandLineRequest(Buffer);
131
132impl GetCommandLineRequest {
133    /// Writes the command line to the target. You have to continue the core manually afterwards.
134    pub fn write_command_line_to_target(
135        &self,
136        core: &mut dyn CoreInterface,
137        cmdline: &str,
138    ) -> Result<(), Error> {
139        let mut buf = cmdline.to_owned().into_bytes();
140        buf.push(0);
141        self.0.write(core, &buf)?;
142
143        // signal to target: status = success
144        write_status(core, 0)?;
145
146        Ok(())
147    }
148}
149
150/// A request to open a file on the host.
151///
152/// Note that this is not implemented by probe-rs yet.
153#[derive(Debug, PartialEq, Eq, Copy, Clone)]
154pub struct OpenRequest {
155    path: ZeroTerminatedString,
156    mode: &'static str,
157}
158
159impl OpenRequest {
160    /// Reads the path from the target.
161    pub fn path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
162        self.path.read(core)
163    }
164
165    /// Reads the raw mode from the target.
166    pub fn mode(&self) -> &'static str {
167        self.mode
168    }
169
170    /// Responds with the opened file handle to the target.
171    pub fn respond_with_handle(
172        &self,
173        core: &mut dyn CoreInterface,
174        handle: NonZeroU32,
175    ) -> Result<(), Error> {
176        write_status(core, handle.get() as i32)
177    }
178}
179
180/// A request to close a file on the host.
181///
182/// Note that this is not implemented by probe-rs yet.
183#[derive(Debug, PartialEq, Eq, Copy, Clone)]
184pub struct CloseRequest {
185    handle: u32,
186}
187
188impl CloseRequest {
189    /// Returns the handle of the file to close
190    pub fn file_handle(&self) -> u32 {
191        self.handle
192    }
193
194    /// Responds with success to the target.
195    pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
196        write_status(core, 0)
197    }
198}
199
200/// A request to write to the console
201#[derive(Debug, PartialEq, Eq, Copy, Clone)]
202pub struct WriteConsoleRequest(pub(crate) ZeroTerminatedString);
203impl WriteConsoleRequest {
204    /// Reads the string from the target
205    pub fn read(&self, core: &mut crate::Core<'_>) -> Result<String, Error> {
206        self.0.read(core)
207    }
208}
209
210/// A request to write to a file on the host
211#[derive(Debug, PartialEq, Eq, Copy, Clone)]
212pub struct WriteRequest {
213    handle: u32,
214    bytes: u32,
215    len: u32,
216}
217
218impl WriteRequest {
219    /// Returns the handle of the file to write to
220    pub fn file_handle(&self) -> u32 {
221        self.handle
222    }
223
224    /// Reads the buffer from the target
225    pub fn read(&self, core: &mut crate::Core<'_>) -> Result<Vec<u8>, Error> {
226        let mut buf = vec![0u8; self.len as usize];
227        core.read(self.bytes as u64, &mut buf)?;
228        Ok(buf)
229    }
230
231    /// Writes the status of the semihosting operation to the return register of the target
232    pub fn write_status(&self, core: &mut dyn CoreInterface, status: i32) -> Result<(), Error> {
233        write_status(core, status)
234    }
235}
236
237/// A request to read from a file on the host
238#[derive(Debug, PartialEq, Eq, Copy, Clone)]
239pub struct ReadRequest {
240    handle: u32,
241    bytes: u32,
242    len: u32,
243}
244
245impl ReadRequest {
246    /// Returns the handle of the file to read from
247    pub fn file_handle(&self) -> u32 {
248        self.handle
249    }
250
251    /// Returns the number of bytes to read
252    pub fn bytes_to_read(&self) -> u32 {
253        self.len
254    }
255
256    /// Writes the buffer to the target
257    pub fn write_buffer_to_target(
258        &self,
259        core: &mut crate::Core<'_>,
260        buf: &[u8],
261    ) -> Result<(), Error> {
262        assert!(buf.len() <= self.len as usize);
263
264        if !buf.is_empty() {
265            core.write(self.bytes as u64, buf)?;
266        }
267
268        let status = match buf.len() {
269            0 => self.len as i32,
270            len if len == self.len as usize => 0,
271            len => len as i32,
272        };
273
274        write_status(core, status)
275    }
276}
277
278/// A request to seek in a file on the host
279#[derive(Debug, PartialEq, Eq, Copy, Clone)]
280pub struct SeekRequest {
281    handle: u32,
282    pos: u32,
283}
284
285impl SeekRequest {
286    /// Returns the handle of the file to seek in
287    pub fn file_handle(&self) -> u32 {
288        self.handle
289    }
290
291    /// Returns the absolute byte position to search to
292    pub fn position(&self) -> u32 {
293        self.pos
294    }
295
296    /// Responds with success to the target
297    pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
298        write_status(core, 0)
299    }
300}
301
302/// A request to read the length of a file on the host
303#[derive(Debug, PartialEq, Eq, Copy, Clone)]
304pub struct FileLengthRequest {
305    handle: u32,
306}
307
308impl FileLengthRequest {
309    /// Returns the handle of the file to seek in
310    pub fn file_handle(&self) -> u32 {
311        self.handle
312    }
313
314    /// Writes the file length to the target
315    pub fn write_length(&self, core: &mut dyn CoreInterface, len: i32) -> Result<(), Error> {
316        write_status(core, len)
317    }
318}
319
320/// A request to remove a file on the host
321#[derive(Debug, PartialEq, Eq, Copy, Clone)]
322pub struct RemoveRequest {
323    path: ZeroTerminatedString,
324}
325
326impl RemoveRequest {
327    /// Reads the path from the target.
328    pub fn path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
329        self.path.read(core)
330    }
331
332    /// Responds with success to the target
333    pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
334        write_status(core, 0)
335    }
336}
337
338/// A request to rename a file on the host
339#[derive(Debug, PartialEq, Eq, Copy, Clone)]
340pub struct RenameRequest {
341    from_path: ZeroTerminatedString,
342    to_path: ZeroTerminatedString,
343}
344
345impl RenameRequest {
346    /// Reads the path of the old file from the target.
347    pub fn from_path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
348        self.from_path.read(core)
349    }
350
351    /// Reads the path for the new file from the target.
352    pub fn to_path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
353        self.to_path.read(core)
354    }
355
356    /// Responds with success to the target
357    pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
358        write_status(core, 0)
359    }
360}
361
362/// A request to read the current time
363#[derive(Debug, PartialEq, Eq, Copy, Clone)]
364pub struct TimeRequest {}
365impl TimeRequest {
366    /// Writes the time to the target
367    pub fn write_time(&self, core: &mut dyn CoreInterface, value: u32) -> Result<(), Error> {
368        write_status(core, value as i32)
369    }
370
371    /// Writes the current time to the target
372    pub fn write_current_time(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
373        let duration = SystemTime::now()
374            .duration_since(SystemTime::UNIX_EPOCH)
375            .expect("Failed to get system time");
376        self.write_time(core, duration.as_secs() as u32)
377    }
378}
379
380/// A request to read the errno
381#[derive(Debug, PartialEq, Eq, Copy, Clone)]
382pub struct ErrnoRequest {}
383impl ErrnoRequest {
384    /// Writes the errno to the target
385    pub fn write_errno(&self, core: &mut dyn CoreInterface, errno: i32) -> Result<(), Error> {
386        // On exit, the RETURN REGISTER contains the value of the C library errno variable.
387        write_status(core, errno)
388    }
389}
390
391fn write_status(core: &mut dyn CoreInterface, value: i32) -> Result<(), crate::Error> {
392    let reg = core.registers().get_argument_register(0).unwrap();
393    core.write_core_reg(reg.into(), RegisterValue::U32(value as u32))?;
394
395    Ok(())
396}
397
398/// When using some semihosting commands, the target usually allocates a buffer for the host to read/write to.
399/// The targets just gives us an address pointing to two u32 values, the address of the buffer and
400/// the length of the buffer.
401#[derive(Debug, PartialEq, Eq, Copy, Clone)]
402pub struct Buffer {
403    buffer_location: u32, // The address where the buffer address and length are stored
404    address: u32,         // The start of the buffer
405    len: u32,             // The length of the buffer
406}
407
408impl Buffer {
409    /// Constructs a new buffer, reading the address and length from the target.
410    pub fn from_block_at(
411        core: &mut dyn CoreInterface,
412        block_addr: u32,
413    ) -> Result<Self, crate::Error> {
414        let mut block: [u32; 2] = [0, 0];
415        core.read_32(block_addr as u64, &mut block)?;
416        Ok(Self {
417            buffer_location: block_addr,
418            address: block[0],
419            len: block[1],
420        })
421    }
422
423    /// Reads the buffer contents from the target.
424    pub fn read(&self, core: &mut dyn CoreInterface) -> Result<Vec<u8>, Error> {
425        let mut buf = vec![0u8; self.len as usize];
426        core.read(self.address as u64, &mut buf[..])?;
427        Ok(buf)
428    }
429
430    /// Writes the passed buffer to the target buffer.
431    /// The buffer must end with \0. Length written to target will not include \0.
432    pub fn write(&self, core: &mut dyn CoreInterface, buf: &[u8]) -> Result<(), Error> {
433        if buf.len() > self.len as usize {
434            return Err(Error::Other("buffer not large enough".to_string()));
435        }
436        if buf.last() != Some(&0) {
437            return Err(Error::Other("last byte of buffer must be 0".to_string()));
438        }
439        core.write_8(self.address as u64, buf)?;
440        let block: [u32; 2] = [self.address, (buf.len() - 1) as u32];
441        core.write_32(self.buffer_location as u64, &block)?;
442        Ok(())
443    }
444}
445
446#[derive(Debug, PartialEq, Eq, Copy, Clone)]
447pub(crate) struct ZeroTerminatedString {
448    pub address: u32,
449    pub length: Option<u32>,
450}
451
452impl ZeroTerminatedString {
453    /// Reads the buffer contents from the target.
454    pub fn read(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
455        let mut bytes = Vec::new();
456
457        if let Some(len) = self.length {
458            bytes = vec![0; len as usize];
459            core.read(self.address as u64, &mut bytes)?;
460        } else {
461            let mut buf = [0; 128];
462            let mut from = self.address as u64;
463
464            loop {
465                core.read(from, &mut buf)?;
466                if let Some(end) = buf.iter().position(|&x| x == 0) {
467                    bytes.extend_from_slice(&buf[..end]);
468                    break;
469                }
470
471                bytes.extend_from_slice(&buf);
472                from += buf.len() as u64;
473            }
474        }
475
476        Ok(String::from_utf8_lossy(&bytes).to_string())
477    }
478}
479
480/// Decodes a semihosting syscall without running the requested action.
481/// Only supports SYS_EXIT, SYS_EXIT_EXTENDED and SYS_GET_CMDLINE at the moment
482pub fn decode_semihosting_syscall(
483    core: &mut dyn CoreInterface,
484) -> Result<SemihostingCommand, Error> {
485    let operation: u32 = core
486        .read_core_reg(core.registers().get_argument_register(0).unwrap().id())?
487        .try_into()?;
488    let parameter: u32 = core
489        .read_core_reg(core.registers().get_argument_register(1).unwrap().id())?
490        .try_into()?;
491
492    tracing::debug!("Semihosting found r0={operation:#x} r1={parameter:#x}");
493
494    // This is defined by the ARM Semihosting Specification:
495    // <https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#semihosting-operations>
496
497    const SYS_GET_CMDLINE: u32 = 0x15;
498    const SYS_EXIT: u32 = 0x18;
499    const SYS_EXIT_EXTENDED: u32 = 0x20;
500    const SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT: u32 = 0x20026;
501    const SYS_OPEN: u32 = 0x01;
502    const SYS_CLOSE: u32 = 0x02;
503    const SYS_WRITEC: u32 = 0x03;
504    const SYS_WRITE0: u32 = 0x04;
505    const SYS_WRITE: u32 = 0x05;
506    const SYS_READ: u32 = 0x06;
507    const SYS_SEEK: u32 = 0x0a;
508    const SYS_FLEN: u32 = 0x0c;
509    const SYS_REMOVE: u32 = 0x0e;
510    const SYS_RENAME: u32 = 0x0f;
511    const SYS_TIME: u32 = 0x11;
512    const SYS_ERRNO: u32 = 0x13;
513
514    Ok(match (operation, parameter) {
515        (SYS_EXIT, SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT) => SemihostingCommand::ExitSuccess,
516        (SYS_EXIT, reason) => SemihostingCommand::ExitError(ExitErrorDetails {
517            reason,
518            exit_status: None,
519            subcode: None,
520        }),
521
522        (SYS_EXIT_EXTENDED, block_address) => {
523            // Parameter points to a block of memory containing two 32-bit words.
524            let mut buf = [0u32; 2];
525            core.read_32(block_address as u64, &mut buf)?;
526            let reason = buf[0];
527            let subcode = buf[1];
528            match (reason, subcode) {
529                (SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT, 0) => SemihostingCommand::ExitSuccess,
530                (SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT, exit_status) => {
531                    SemihostingCommand::ExitError(ExitErrorDetails {
532                        reason,
533                        exit_status: Some(exit_status),
534                        subcode: None,
535                    })
536                }
537                (reason, subcode) => SemihostingCommand::ExitError(ExitErrorDetails {
538                    reason,
539                    exit_status: None,
540                    subcode: Some(subcode),
541                }),
542            }
543        }
544
545        (SYS_GET_CMDLINE, block_address) => {
546            // signal to target: status = failure, in case the application does not answer this request
547            // -1 is the error value for SYS_GET_CMDLINE
548            write_status(core, -1)?;
549            SemihostingCommand::GetCommandLine(GetCommandLineRequest(Buffer::from_block_at(
550                core,
551                block_address,
552            )?))
553        }
554
555        (SYS_OPEN, pointer) => {
556            let [string, mode, str_len] = param(core, pointer)?;
557
558            // signal to target: status = failure, in case the application does not answer this request
559            // -1 is the error value for SYS_OPEN
560            write_status(core, -1)?;
561            SemihostingCommand::Open(OpenRequest {
562                path: ZeroTerminatedString {
563                    address: string,
564                    length: Some(str_len),
565                },
566                mode: match mode {
567                    0 => "r",
568                    1 => "rb",
569                    2 => "r+",
570                    3 => "r+b",
571                    4 => "w",
572                    5 => "wb",
573                    6 => "w+",
574                    7 => "w+b",
575                    8 => "a",
576                    9 => "ab",
577                    10 => "a+",
578                    11 => "a+b",
579                    _ => "unknown",
580                },
581            })
582        }
583
584        (SYS_CLOSE, pointer) => {
585            let [handle] = param(core, pointer)?;
586            // signal to target: status = failure, in case the application does not answer this request
587            // -1 is the error value for SYS_CLOSE
588            write_status(core, -1)?;
589            SemihostingCommand::Close(CloseRequest { handle })
590        }
591
592        (SYS_WRITEC, pointer) => {
593            SemihostingCommand::WriteConsole(WriteConsoleRequest(ZeroTerminatedString {
594                address: pointer,
595                length: Some(1),
596            }))
597            // no response is given
598        }
599
600        (SYS_WRITE0, pointer) => {
601            SemihostingCommand::WriteConsole(WriteConsoleRequest(ZeroTerminatedString {
602                address: pointer,
603                length: None,
604            }))
605            // no response is given
606        }
607
608        (SYS_WRITE, pointer) => {
609            let [handle, bytes, len] = param(core, pointer)?;
610            // signal to target: status = failure, in case the application does not answer this request
611            write_status(core, -1)?;
612            SemihostingCommand::Write(WriteRequest { handle, bytes, len })
613        }
614
615        (SYS_READ, pointer) => {
616            let [handle, bytes, len] = param(core, pointer)?;
617            // signal to target: status = failure, in case the application does not answer this request
618            write_status(core, -1)?;
619            SemihostingCommand::Read(ReadRequest { handle, bytes, len })
620        }
621
622        (SYS_SEEK, pointer) => {
623            let [handle, pos] = param(core, pointer)?;
624            // signal to target: status = failure, in case the application does not answer this request
625            write_status(core, -1)?;
626            SemihostingCommand::Seek(SeekRequest { handle, pos })
627        }
628
629        (SYS_FLEN, pointer) => {
630            let [handle] = param(core, pointer)?;
631            // signal to target: status = failure, in case the application does not answer this request
632            write_status(core, -1)?;
633            SemihostingCommand::FileLength(FileLengthRequest { handle })
634        }
635
636        (SYS_REMOVE, pointer) => {
637            let [path, len] = param(core, pointer)?;
638            // signal to target: status = failure, in case the application does not answer this request
639            write_status(core, -1)?;
640            SemihostingCommand::Remove(RemoveRequest {
641                path: ZeroTerminatedString {
642                    address: path,
643                    length: Some(len),
644                },
645            })
646        }
647
648        (SYS_RENAME, pointer) => {
649            let [from_path, from_len, to_path, to_len] = param(core, pointer)?;
650            // signal to target: status = failure, in case the application does not answer this request
651            write_status(core, -1)?;
652            SemihostingCommand::Rename(RenameRequest {
653                from_path: ZeroTerminatedString {
654                    address: from_path,
655                    length: Some(from_len),
656                },
657                to_path: ZeroTerminatedString {
658                    address: to_path,
659                    length: Some(to_len),
660                },
661            })
662        }
663
664        (SYS_TIME, 0) => SemihostingCommand::Time(TimeRequest {}),
665
666        (SYS_ERRNO, 0) => SemihostingCommand::Errno(ErrnoRequest {}),
667
668        _ => {
669            // signal to target: status = failure, in case the application does not answer this request
670            // It is not guaranteed that a value of -1 will be treated as an error by the target, but it is a common value to indicate an error.
671            write_status(core, -1)?;
672
673            tracing::debug!(
674                "Unknown semihosting operation={operation:04x} parameter={parameter:04x}"
675            );
676            SemihostingCommand::Unknown(UnknownCommandDetails {
677                operation,
678                parameter,
679            })
680        }
681    })
682}
683
684fn param<const N: usize>(
685    core: &mut dyn CoreInterface,
686    pointer: u32,
687) -> Result<[u32; N], crate::Error> {
688    let mut buf = [0; N];
689    core.read_32(pointer as u64, &mut buf)?;
690    Ok(buf)
691}