libmicroemu 0.2.0
ARM Microcontroller Emulator Library
Loading...
Searching...
No Matches
semihosting.h
1#pragma once
2
3#include "libmicroemu/internal/i_breakpoint.h"
5#include "libmicroemu/internal/utils/const_string_builder.h"
6#include "libmicroemu/internal/utils/memory_helpers.h"
7#include "libmicroemu/logger.h"
8#include "libmicroemu/types.h"
9
10#include <string.h>
11#include <time.h>
12#include <tuple>
13
14#undef _DISABLE_EXT_EXIT
15
16namespace libmicroemu::internal {
17
18enum {
19 // File operations
20 kSysOpen = 0x01U, //- Open a file or stream on the host system.
21 kSysIsTTY = 0x09U, //- Check whether a file handle is associated with a file or
22 // a stream/terminal such as stdout.
23 kSysWrite = 0x05U, //- Write to a file or stream.
24 kSysRead = 0x06U, //- Read from a file at the current cursor position.
25 kSysClose = 0x02U, //- Closes a file on the host which has been opened by SysOPEN.
26 kSysFLen = 0x0CU, //- Get the length of a file.
27 kSysSeek = 0x0AU, //- Set the file cursor to a given position in a file.
28 kSysTmpNam = 0x0DU, //- Get a temporary absolute file path to create a temporary file.
29 kSysRemove = 0x0EU, //- Remove a file on the host system. Possibly insecure!
30 kSysRename = 0x0FU, //- Rename a file on the host system. Possibly insecure!
31 // Terminal I/O operations
32 kSysWriteC = 0x03U, //- Write one character to the debug terminal.
33 kSysWrite0 = 0x04U, //- Write a 0-terminated string to the debug terminal.
34 kSysReadC = 0x07U, //- Read one character from the debug terminal.
35 // Time operations
36 kSysClock = 0x10U,
37 kSysElapsed = 0x30U,
38 kSysTickFreq = 0x31U,
39 kSysTime = 0x11U,
40 // System/Misc. operations
41 kSysErrNo = 0x13U, //- Returns the value of the C library errno variable that
42 // is associated with the semihosting implementation.
43 kSysGetCmdLine = 0x15U, //- Get commandline parameters for the application to
44 // run with=argc and argv for main())
45 kSysHeapInfo = 0x16U,
46 kSysExit = 0x18U, // An application calls this operation to report an
47 // exception to the debugger directly.
48 kSysIsError = 0x08U,
49 kSysSystem = 0x12U,
50
51 // Extended feature - must be enabled
52 kSysExitExtended = 0x20U,
53};
54
55enum class ReasonCodes : uint32_t {
56 // Hardware reason codes
57 kADPStoppedBranchThroughZero = 0x20000U,
58 kADPStoppedUndefinedInstr = 0x20001U,
59 kADPStoppedSoftwareInterrupt = 0x20002U,
60 kADPStoppedPrefetchAbort = 0x20003U,
61 kADPStoppedDataAbort = 0x20004U,
62 kADPStoppedAddressException = 0x20005U,
63 kADPStoppedIRQ = 0x20006U,
64 kADPStoppedFIQ = 0x20007U,
65
66 // Software reason codes
67 kADPStoppedBreakPoint = 0x20020U,
68 kADPStoppedWatchPoint = 0x20021U,
69 kADPStoppedStepComplete = 0x20022U,
70 kADPStoppedRunTimeErrorUnknown = 0x20023U,
71 kADPStoppedInternalError = 0x20024U,
72 kADPStoppedUserInterruption = 0x20025U,
73 kADPStoppedApplicationExit = 0x20026U,
74 kADPStoppedStackOverflow = 0x20027U,
75 kADPStoppedDivisionByZero = 0x20028U,
76 kADPStoppedOSSpecific = 0x20029U
77};
78
79constexpr u32 kHandleStdin = 1U;
80constexpr u32 kHandleStdout = 2U;
81constexpr u32 kHandleStderr = 3U;
82constexpr u32 kHandleSemihostFeatures = 4U;
83
84static char kFeatureData[] = {
85 0x53U, // Magic Byte 0
86 0x48U, // Magic Byte 1
87 0x46U, // Magic Byte 2
88 0x42U, // Magic Byte 3
89 0x3U // feature byte 0 : SH_EXT_EXIT_EXTENDED + SH_EXT_STDOUT_STDERR
90};
91
92template <typename TCpuAccessor, typename TBus, typename TLogger = NullLogger>
93class Semihosting : public IBreakpoint {
94public:
98 Semihosting(TCpuAccessor &cpua, TBus bus) : bus_(bus), cpua_(cpua) {}
99
100 template <u32 N> Result<std::array<u32, N>> ReadR1Words();
101
102 template <> Result<std::array<u32, 3>> ReadR1Words<3U>() {
103 auto r1 = cpua_.template ReadRegister<RegisterId::kR1>();
104 auto mem = MemoryHelpers::ReadMemory(cpua_, bus_, r1, r1 + 0x4, r1 + 0x8);
105 return mem;
106 }
107
108 template <> Result<std::array<u32, 2>> ReadR1Words<2U>() {
109 auto r1 = cpua_.template ReadRegister<RegisterId::kR1>();
110 auto mem = MemoryHelpers::ReadMemory(cpua_, bus_, r1, r1 + 0x4);
111 return mem;
112 }
113
114 template <> Result<std::array<u32, 1>> ReadR1Words<1U>() {
115 auto r1 = cpua_.template ReadRegister<RegisterId::kR1>();
116 auto mem = MemoryHelpers::ReadMemory(cpua_, bus_, r1);
117 return mem;
118 }
119
121 i32 ret_r0;
122 BkptFlagsSet bkpt_flags;
123 };
124
125 Result<SemihostResult> CallSemihost(const uint32_t &r0) {
126 i32 sh_ret = -1U;
127 BkptFlagsSet bkpt_flags = 0U;
128 bkpt_flags |= static_cast<BkptFlagsSet>(BkptFlags::kOmitException);
129 switch (r0) {
130
131 case kSysHeapInfo: {
132 LOG_DEBUG(TLogger, "kSysHeapInfo(0x%0x)", r0);
133 sh_ret = r0; // On exit, R1 contains the address of the pointer to the
134 // structure.
135 break;
136 }
137
138 case kSysOpen: {
139 TRY_ASSIGN(mem3, SemihostResult, ReadR1Words<3>());
140 const auto &ptr = mem3[0];
141 const auto &mode = mem3[1];
142 const auto &w_len = mem3[2];
143
144 char buf[kBufferLen];
145 // keep one char reserve for null-termination
146 MemoryHelpers::CpyFromEmuMem(cpua_, bus_, buf, sizeof(buf) - 1, ptr, w_len);
147 buf[w_len] = '\0';
148 LOG_DEBUG(TLogger, "kSysOpen(0x%0x) - 0x%0x 0x%0x 0x%0x - '%s'", r0, ptr, mode, w_len, buf);
149 u32 result = -1U;
150 if (strcmp(buf, ":tt") == 0) {
151 if ((mode >= 0U) && (mode <= 3U)) {
152 result = kHandleStdin;
153 } else if ((mode >= 4U) && (mode <= 7U)) {
154 result = kHandleStdout;
155 } else if ((mode >= 8U) && (mode <= 11U)) {
156 result = kHandleStderr;
157 } else {
158 return Err<SemihostResult>(StatusCode::kOutOfRange);
159 }
160 } else if (strcmp(buf, ":semihosting-features") == 0) {
161 result = kHandleSemihostFeatures;
162 } else {
163 return Err<SemihostResult>(StatusCode::kOpenFileFailed);
164 }
165 // return handle
166 sh_ret = result;
167 break;
168 }
169 case kSysWrite: {
170 TRY_ASSIGN(mem3, SemihostResult, ReadR1Words<3>());
171 const auto &fhandle = mem3[0];
172 const auto &ptr = mem3[1];
173 const auto &w_len = mem3[2];
174
175 if ((fhandle != kHandleStdout) && (fhandle != kHandleStderr)) {
176 // currently only support Write to stdout and stderr
177 return Err<SemihostResult>(StatusCode::kUnsuporrted);
178 }
179 char buf[kBufferLen];
180 // keep one char reserve for null-termination
182 MemoryHelpers::CpyFromEmuMem(cpua_, bus_, buf, sizeof(buf) - 1, ptr, w_len));
183 buf[w_len] = '\0';
184
185 LOG_TRACE(TLogger, "kSysWrite(0x%0x)- 0x%0x 0x%0x 0x%0x", r0, fhandle, ptr, w_len, buf);
186 LOG_INFO(TLogger, "stdout << '%s'", buf);
187 printf("%s", buf);
188
189 // 0 indicates everything is ok
190 sh_ret = 0U;
191 break;
192 }
193 case kSysRead: {
194 TRY_ASSIGN(mem3, SemihostResult, ReadR1Words<3>());
195 const auto &fhandle = mem3[0];
196 const auto &ptr = mem3[1];
197 const auto &r_len = mem3[2];
198
199 LOG_DEBUG(TLogger, "kSysRead(0x%0x)- 0x%0x 0x%0x 0x%0x", r0, fhandle, ptr, r_len);
200
201 if (fhandle == kHandleSemihostFeatures) {
202
203 auto *feature_data = &kFeatureData[semihost_features_position_];
204 auto features_data_size = sizeof(kFeatureData) - semihost_features_position_;
205
207 read_bytes, SemihostResult,
208 MemoryHelpers::CpyToEmuMem(cpua_, bus_, ptr, r_len, feature_data, features_data_size));
209
210 semihost_features_position_ += read_bytes;
211 // 0 indicates everything is ok
212 sh_ret = 0U;
213 } else {
214 return Err<SemihostResult>(StatusCode::kUnsuporrted);
215 }
216 break;
217 }
218 case kSysIsTTY: {
219 TRY_ASSIGN(mem1, SemihostResult, ReadR1Words<1>());
220 const auto &fhandle = mem1[0];
221
222 LOG_DEBUG(TLogger, "kSysIsTTY(0x%0x) - 0x%0x", r0, fhandle);
223
224 if (fhandle != kHandleStdout) {
225 // currently only support Write to stdout
226 return Err<SemihostResult>(StatusCode::kUnsuporrted);
227 }
228
229 // 1 if the handle identifies an interactive device
230 sh_ret = 1U;
231 break;
232 }
233 case kSysFLen: {
234 TRY_ASSIGN(mem1, SemihostResult, ReadR1Words<1>());
235 const auto &fhandle = mem1[0];
236 LOG_DEBUG(TLogger, "kSysFLen(0x%0x) - 0x%0x", r0, fhandle);
237
238 if ((fhandle == kHandleStdin) || (fhandle == kHandleStdout) || (fhandle == kHandleStderr)) {
239 sh_ret = 0U;
240 break;
241 }
242 if (fhandle == kHandleSemihostFeatures) {
243#ifdef _DISABLE_EXT_EXIT
244 sh_ret = 0U; // no extended features
245#else
246 sh_ret = sizeof(kFeatureData); // x bytes needed to store the feature sequence
247#endif
248 break;
249 }
250 return Err<SemihostResult>(StatusCode::kUnexpected);
251 break;
252 }
253 case kSysSeek: {
254 TRY_ASSIGN(mem2, SemihostResult, ReadR1Words<2>());
255 const auto &fhandle = mem2[0];
256 const auto &fpos = mem2[1];
257 LOG_DEBUG(TLogger, "kSysSeek(0x%0x) - 0x%0x - %u", r0, fhandle, fpos);
258
259 if (fhandle == kHandleSemihostFeatures) {
260 if (fpos < sizeof(kFeatureData)) {
261 semihost_features_position_ = fpos;
262 sh_ret = 0U;
263 } else {
264 return Err<SemihostResult>(StatusCode::kOutOfRange);
265 }
266
267 } else {
268 return Err<SemihostResult>(StatusCode::kUnexpected);
269 }
270
271 break;
272 }
273 case kSysErrNo: {
274 LOG_ERROR(TLogger, "kSysErrNo(0x%0x)", r0);
275
276 break;
277 }
278 case kSysClock: {
279 auto reason_code = cpua_.template ReadRegister<RegisterId::kR1>();
280 if (reason_code != 0x0) {
281 return Err<SemihostResult>(StatusCode::kUnexpected);
282 }
283 u32 ticks = clock();
284
285 LOG_DEBUG(TLogger, "kSysClock(0x%0x) - host_clock:%u, CLOCKS_PER_SEC:%u", r0,
286 static_cast<unsigned int>(ticks), static_cast<unsigned int>(CLOCKS_PER_SEC));
287
288 uint64_t ticks_64 = static_cast<u64>(ticks);
289 // caculate the time in centiseconds. This is expected by libgloss
290 uint64_t centiseconds = (ticks_64 * 100) / CLOCKS_PER_SEC;
291
292 sh_ret = static_cast<i32>(centiseconds);
293 break;
294 }
295
296 case kSysExit: {
297 LOG_INFO(TLogger, "kSysExit(0x%0x)", r0);
298
299 auto reason_code = cpua_.template ReadRegister<RegisterId::kR1>();
300 status_code_ = 0U; // no status code available
301 if (reason_code == static_cast<u32>(ReasonCodes::kADPStoppedApplicationExit)) {
302 bkpt_flags |= static_cast<BkptFlagsSet>(BkptFlags::kRequestExit);
303 } else {
304 bkpt_flags |= static_cast<BkptFlagsSet>(BkptFlags::kRequestErrorExit);
305 }
306 break;
307 }
308
309 case kSysExitExtended: {
310 TRY_ASSIGN(mem2, SemihostResult, ReadR1Words<2>());
311 const auto &reason_code = mem2[0];
312 const auto &reason_subcode = mem2[1];
313 LOG_INFO(TLogger, "kSysExitExtended(0x%0x) - reason_code: 0x%x - reason_subcode: 0x%x", r0,
314 reason_code, reason_subcode);
315 status_code_ = reason_subcode;
316 if (reason_code == static_cast<u32>(ReasonCodes::kADPStoppedApplicationExit)) {
317 bkpt_flags |= static_cast<BkptFlagsSet>(BkptFlags::kRequestExit);
318 } else {
319 bkpt_flags |= static_cast<BkptFlagsSet>(BkptFlags::kRequestErrorExit);
320 }
321 break;
322 }
323
324 case kSysGetCmdLine: {
325
326 TRY_ASSIGN(mem2, SemihostResult, ReadR1Words<2>());
327 const auto &w1 = mem2[0];
328 const auto &w2 = mem2[1];
329 static_cast<void>(w1);
330 static_cast<void>(w2);
331 LOG_DEBUG(TLogger, "kSysGetCmdLine(0x%0x) - 0x%0x 0x%0x", r0, w1, w2);
332
333 sh_ret = -1U; // accepted but not supported
334 break;
335 }
336 case kSysClose: {
337 TRY_ASSIGN(mem1, SemihostResult, ReadR1Words<1>());
338 const auto &fhandle = mem1[0];
339
340 if ((fhandle != kHandleStdin) && (fhandle != kHandleStdout) && (fhandle != kHandleStderr) &&
341 (fhandle != kHandleSemihostFeatures)) {
342 return Err<SemihostResult>(StatusCode::kUnexpected);
343 }
344 LOG_DEBUG(TLogger, "kSysClose(0x%0x) - 0x%0x", r0, fhandle);
345 sh_ret = 0U;
346 break;
347 }
348
349 default: {
350 LOG_ERROR(TLogger, "Unknown(0x%0x)", r0);
351 return Err<SemihostResult>(StatusCode::kUnsuporrted);
352 sh_ret = -1U; // signals error
353 break;
354 }
355 }
356
357 return Ok<SemihostResult>(SemihostResult{sh_ret, bkpt_flags});
358 }
359
360 virtual Result<BkptFlagsSet> Call(const uint32_t &imm32) override {
361 if (imm32 != 0xabu) {
362 return Ok<u8>(0U); // no semihosting call
363 }
364 auto r0 = cpua_.template ReadRegister<RegisterId::kR0>();
365 TRY_ASSIGN(sh_res, u8, CallSemihost(r0));
366 cpua_.WriteRegister(RegisterId::kR0, sh_res.ret_r0);
367
368 return Ok(sh_res.bkpt_flags);
369 }
370
371 virtual uint32_t GetExitStatusCode() const override { return status_code_; }
372
373private:
374 static constexpr u32 kBufferLen = 128U;
375
376 TBus bus_;
377 TCpuAccessor &cpua_;
378 u32 file_id_{0xa};
379 i32 status_code_{0U};
380 u32 semihost_features_position_{0U};
381};
382
383} // namespace libmicroemu::internal
Definition i_breakpoint.h:9
Semihosting(TCpuAccessor &cpua, TBus bus)
Constructs a Semihosting object.
Definition semihosting.h:98
The libmicroemu::internal namespace contains all classes and functions of libmicroemu which are priva...
Definition bkpt_flags.h:6
@ kUnsuporrted
Operation or feature is unsupported.
Definition status_code.h:31
@ kOpenFileFailed
Failed to open the file.
Definition status_code.h:48
@ kUnexpected
An unexpected error occurred.
Definition status_code.h:28
@ kOutOfRange
Value is out of the allowed range.
Definition status_code.h:39
Contains the Result class which is used to return the result of a function.
#define TRY_ASSIGN(NAME, OUT_TYPE, CALL)
Try to assign a value to a variable and return an error if the assignment fails.
Definition result.h:123
#define TRY(OUT_TYPE, CALL)
Try to call a function and return an error if the call fails.
Definition result.h:138
Definition result.h:15
Definition result.h:15