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