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;
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 the console.
33    Write(WriteRequest),
34
35    /// The target indicated that it would like to read the value of errno.
36    Errno(ErrnoRequest),
37
38    /// The target indicated that it would like to run a semihosting operation which we don't support yet.
39    Unknown(UnknownCommandDetails),
40}
41
42/// Details of a semihosting exit with error
43#[derive(Debug, PartialEq, Eq, Copy, Clone)]
44pub struct ExitErrorDetails {
45    /// Some application specific exit reason:
46    /// <https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#651entry-32-bit>
47    pub reason: u32,
48
49    /// The exit status of the application, if present (only if reason == `ADP_Stopped_ApplicationExit` `0x20026`).
50    /// This is an exit status code, as passed to the C standard library exit() function.
51    pub exit_status: Option<u32>,
52
53    /// The subcode of the exit, if present (only if reason != `ADP_Stopped_ApplicationExit` `0x20026`).
54    pub subcode: Option<u32>,
55}
56
57impl std::fmt::Display for ExitErrorDetails {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        write!(f, "reason: {:#x}", self.reason)?;
60        if let Some(exit_status) = self.exit_status {
61            write!(f, ", exit_status: {}", exit_status)?;
62        }
63        if let Some(subcode) = self.subcode {
64            write!(f, ", subcode: {:#x}", subcode)?;
65        }
66        Ok(())
67    }
68}
69
70/// Details of a semihosting operation that we don't support yet
71#[derive(Debug, PartialEq, Eq, Copy, Clone)]
72pub struct UnknownCommandDetails {
73    /// The semihosting operation requested
74    pub operation: u32,
75
76    /// The parameter to the semihosting operation
77    pub parameter: u32,
78}
79
80impl UnknownCommandDetails {
81    /// Returns the buffer pointed-to by the parameter of the semihosting operation
82    pub fn get_buffer(&self, core: &mut dyn CoreInterface) -> Result<Buffer, Error> {
83        Buffer::from_block_at(core, self.parameter)
84    }
85
86    /// Writes the status of the semihosting operation to the return register of the target
87    pub fn write_status(&self, core: &mut dyn CoreInterface, status: i32) -> Result<(), Error> {
88        write_status(core, status)
89    }
90}
91
92/// A request to read the command line arguments from the target
93#[derive(Debug, PartialEq, Eq, Copy, Clone)]
94pub struct GetCommandLineRequest(Buffer);
95
96impl GetCommandLineRequest {
97    /// Writes the command line to the target. You have to continue the core manually afterwards.
98    pub fn write_command_line_to_target(
99        &self,
100        core: &mut dyn CoreInterface,
101        cmdline: &str,
102    ) -> Result<(), Error> {
103        let mut buf = cmdline.to_owned().into_bytes();
104        buf.push(0);
105        self.0.write(core, &buf)?;
106
107        // signal to target: status = success
108        write_status(core, 0)?;
109
110        Ok(())
111    }
112}
113
114/// A request to open a file on the host.
115///
116/// Note that this is not implemented by probe-rs yet.
117#[derive(Debug, PartialEq, Eq, Copy, Clone)]
118pub struct OpenRequest {
119    path: ZeroTerminatedString,
120    mode: &'static str,
121}
122
123impl OpenRequest {
124    /// Reads the path from the target.
125    pub fn path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
126        self.path.read(core)
127    }
128
129    /// Reads the raw mode from the target.
130    pub fn mode(&self) -> &'static str {
131        self.mode
132    }
133
134    /// Responds with the opened file handle to the target.
135    pub fn respond_with_handle(
136        &self,
137        core: &mut dyn CoreInterface,
138        handle: NonZeroU32,
139    ) -> Result<(), Error> {
140        write_status(core, handle.get() as i32)
141    }
142}
143
144/// A request to open a file on the host.
145///
146/// Note that this is not implemented by probe-rs yet.
147#[derive(Debug, PartialEq, Eq, Copy, Clone)]
148pub struct CloseRequest {
149    pointer: u32,
150}
151
152impl CloseRequest {
153    /// Returns the handle of the file to close
154    pub fn file_handle(&self, core: &mut dyn CoreInterface) -> Result<u32, Error> {
155        core.read_word_32(self.pointer as u64)
156    }
157
158    /// Responds with success to the target.
159    pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
160        write_status(core, 0)
161    }
162}
163
164/// A request to write to the console
165#[derive(Debug, PartialEq, Eq, Copy, Clone)]
166pub struct WriteConsoleRequest(pub(crate) ZeroTerminatedString);
167impl WriteConsoleRequest {
168    /// Reads the string from the target
169    pub fn read(&self, core: &mut crate::Core<'_>) -> Result<String, Error> {
170        self.0.read(core)
171    }
172}
173
174/// A request to write to the console
175#[derive(Debug, PartialEq, Eq, Copy, Clone)]
176pub struct WriteRequest {
177    handle: u32,
178    bytes: u32,
179    len: u32,
180}
181impl WriteRequest {
182    /// Returns the handle of the file to write to
183    pub fn file_handle(&self) -> u32 {
184        self.handle
185    }
186
187    /// Reads the buffer from the target
188    pub fn read(&self, core: &mut crate::Core<'_>) -> Result<Vec<u8>, Error> {
189        let mut buf = vec![0u8; self.len as usize];
190        core.read(self.bytes as u64, &mut buf)?;
191        Ok(buf)
192    }
193
194    /// Writes the status of the semihosting operation to the return register of the target
195    pub fn write_status(&self, core: &mut dyn CoreInterface, status: i32) -> Result<(), Error> {
196        write_status(core, status)
197    }
198}
199
200/// A request to read the errno
201#[derive(Debug, PartialEq, Eq, Copy, Clone)]
202pub struct ErrnoRequest {}
203impl ErrnoRequest {
204    /// Writes the errno to the target
205    pub fn write_errno(&self, core: &mut dyn CoreInterface, errno: i32) -> Result<(), Error> {
206        // On exit, the RETURN REGISTER contains the value of the C library errno variable.
207        write_status(core, errno)
208    }
209}
210
211fn write_status(core: &mut dyn CoreInterface, value: i32) -> Result<(), crate::Error> {
212    let reg = core.registers().get_argument_register(0).unwrap();
213    core.write_core_reg(reg.into(), RegisterValue::U32(value as u32))?;
214
215    Ok(())
216}
217
218/// When using some semihosting commands, the target usually allocates a buffer for the host to read/write to.
219/// The targets just gives us an address pointing to two u32 values, the address of the buffer and
220/// the length of the buffer.
221#[derive(Debug, PartialEq, Eq, Copy, Clone)]
222pub struct Buffer {
223    buffer_location: u32, // The address where the buffer address and length are stored
224    address: u32,         // The start of the buffer
225    len: u32,             // The length of the buffer
226}
227
228impl Buffer {
229    /// Constructs a new buffer, reading the address and length from the target.
230    pub fn from_block_at(
231        core: &mut dyn CoreInterface,
232        block_addr: u32,
233    ) -> Result<Self, crate::Error> {
234        let mut block: [u32; 2] = [0, 0];
235        core.read_32(block_addr as u64, &mut block)?;
236        Ok(Self {
237            buffer_location: block_addr,
238            address: block[0],
239            len: block[1],
240        })
241    }
242
243    /// Reads the buffer contents from the target.
244    pub fn read(&self, core: &mut dyn CoreInterface) -> Result<Vec<u8>, Error> {
245        let mut buf = vec![0u8; self.len as usize];
246        core.read(self.address as u64, &mut buf[..])?;
247        Ok(buf)
248    }
249
250    /// Writes the passed buffer to the target buffer.
251    /// The buffer must end with \0. Length written to target will not include \0.
252    pub fn write(&self, core: &mut dyn CoreInterface, buf: &[u8]) -> Result<(), Error> {
253        if buf.len() > self.len as usize {
254            return Err(Error::Other("buffer not large enough".to_string()));
255        }
256        if buf.last() != Some(&0) {
257            return Err(Error::Other("last byte of buffer must be 0".to_string()));
258        }
259        core.write_8(self.address as u64, buf)?;
260        let block: [u32; 2] = [self.address, (buf.len() - 1) as u32];
261        core.write_32(self.buffer_location as u64, &block)?;
262        Ok(())
263    }
264}
265
266#[derive(Debug, PartialEq, Eq, Copy, Clone)]
267pub(crate) struct ZeroTerminatedString {
268    pub address: u32,
269    pub length: Option<u32>,
270}
271
272impl ZeroTerminatedString {
273    /// Reads the buffer contents from the target.
274    pub fn read(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
275        let mut bytes = Vec::new();
276
277        if let Some(len) = self.length {
278            bytes = vec![0; len as usize];
279            core.read(self.address as u64, &mut bytes)?;
280        } else {
281            let mut buf = [0; 128];
282            let mut from = self.address as u64;
283
284            loop {
285                core.read(from, &mut buf)?;
286                if let Some(end) = buf.iter().position(|&x| x == 0) {
287                    bytes.extend_from_slice(&buf[..end]);
288                    break;
289                }
290
291                bytes.extend_from_slice(&buf);
292                from += buf.len() as u64;
293            }
294        }
295
296        Ok(String::from_utf8_lossy(&bytes).to_string())
297    }
298}
299
300/// Decodes a semihosting syscall without running the requested action.
301/// Only supports SYS_EXIT, SYS_EXIT_EXTENDED and SYS_GET_CMDLINE at the moment
302pub fn decode_semihosting_syscall(
303    core: &mut dyn CoreInterface,
304) -> Result<SemihostingCommand, Error> {
305    let operation: u32 = core
306        .read_core_reg(core.registers().get_argument_register(0).unwrap().id())?
307        .try_into()?;
308    let parameter: u32 = core
309        .read_core_reg(core.registers().get_argument_register(1).unwrap().id())?
310        .try_into()?;
311
312    tracing::debug!("Semihosting found r0={operation:#x} r1={parameter:#x}");
313
314    // This is defined by the ARM Semihosting Specification:
315    // <https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#semihosting-operations>
316
317    const SYS_GET_CMDLINE: u32 = 0x15;
318    const SYS_EXIT: u32 = 0x18;
319    const SYS_EXIT_EXTENDED: u32 = 0x20;
320    const SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT: u32 = 0x20026;
321    const SYS_OPEN: u32 = 0x01;
322    const SYS_CLOSE: u32 = 0x02;
323    const SYS_WRITEC: u32 = 0x03;
324    const SYS_WRITE0: u32 = 0x04;
325    const SYS_WRITE: u32 = 0x05;
326    const SYS_ERRNO: u32 = 0x13;
327
328    Ok(match (operation, parameter) {
329        (SYS_EXIT, SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT) => SemihostingCommand::ExitSuccess,
330        (SYS_EXIT, reason) => SemihostingCommand::ExitError(ExitErrorDetails {
331            reason,
332            exit_status: None,
333            subcode: None,
334        }),
335
336        (SYS_EXIT_EXTENDED, block_address) => {
337            // Parameter points to a block of memory containing two 32-bit words.
338            let mut buf = [0u32; 2];
339            core.read_32(block_address as u64, &mut buf)?;
340            let reason = buf[0];
341            let subcode = buf[1];
342            match (reason, subcode) {
343                (SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT, 0) => SemihostingCommand::ExitSuccess,
344                (SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT, exit_status) => {
345                    SemihostingCommand::ExitError(ExitErrorDetails {
346                        reason,
347                        exit_status: Some(exit_status),
348                        subcode: None,
349                    })
350                }
351                (reason, subcode) => SemihostingCommand::ExitError(ExitErrorDetails {
352                    reason,
353                    exit_status: None,
354                    subcode: Some(subcode),
355                }),
356            }
357        }
358
359        (SYS_GET_CMDLINE, block_address) => {
360            // signal to target: status = failure, in case the application does not answer this request
361            // -1 is the error value for SYS_GET_CMDLINE
362            write_status(core, -1)?;
363            SemihostingCommand::GetCommandLine(GetCommandLineRequest(Buffer::from_block_at(
364                core,
365                block_address,
366            )?))
367        }
368
369        (SYS_OPEN, pointer) => {
370            let [string, mode, str_len] = param3(core, pointer)?;
371
372            // signal to target: status = failure, in case the application does not answer this request
373            // -1 is the error value for SYS_OPEN
374            write_status(core, -1)?;
375            SemihostingCommand::Open(OpenRequest {
376                path: ZeroTerminatedString {
377                    address: string,
378                    length: Some(str_len),
379                },
380                mode: match mode {
381                    0 => "r",
382                    1 => "rb",
383                    2 => "r+",
384                    3 => "r+b",
385                    4 => "w",
386                    5 => "wb",
387                    6 => "w+",
388                    7 => "w+b",
389                    8 => "a",
390                    9 => "ab",
391                    10 => "a+",
392                    11 => "a+b",
393                    _ => "unknown",
394                },
395            })
396        }
397
398        (SYS_CLOSE, pointer) => {
399            // signal to target: status = failure, in case the application does not answer this request
400            // -1 is the error value for SYS_CLOSE
401            write_status(core, -1)?;
402            SemihostingCommand::Close(CloseRequest { pointer })
403        }
404
405        (SYS_WRITEC, pointer) => {
406            SemihostingCommand::WriteConsole(WriteConsoleRequest(ZeroTerminatedString {
407                address: pointer,
408                length: Some(1),
409            }))
410            // no response is given
411        }
412
413        (SYS_WRITE0, pointer) => {
414            SemihostingCommand::WriteConsole(WriteConsoleRequest(ZeroTerminatedString {
415                address: pointer,
416                length: None,
417            }))
418            // no response is given
419        }
420
421        (SYS_WRITE, pointer) => {
422            let [handle, bytes, len] = param3(core, pointer)?;
423            // signal to target: status = failure, in case the application does not answer this request
424            write_status(core, -1)?;
425            SemihostingCommand::Write(WriteRequest { handle, bytes, len })
426        }
427
428        (SYS_ERRNO, 0) => SemihostingCommand::Errno(ErrnoRequest {}),
429
430        _ => {
431            // signal to target: status = failure, in case the application does not answer this request
432            // 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.
433            write_status(core, -1)?;
434
435            tracing::debug!(
436                "Unknown semihosting operation={operation:04x} parameter={parameter:04x}"
437            );
438            SemihostingCommand::Unknown(UnknownCommandDetails {
439                operation,
440                parameter,
441            })
442        }
443    })
444}
445
446fn param3(core: &mut dyn CoreInterface, pointer: u32) -> Result<[u32; 3], crate::Error> {
447    let mut buf = [0; 3];
448    core.read_32(pointer as u64, &mut buf)?;
449    Ok(buf)
450}