技术控

    今日:88| 主题:49270
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Semi-hosting on ARM with Rust

[复制链接]
鸭梨 发表于 2016-10-20 02:09:31
133 2

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
by    Marc Brinkmannwith contributions from    Philipp Oppermann· October 18, 2016  
  If a technology is in use for two decades and still has no Wikipedia entry, it seems safe to call it "a bit obscure".    Semi-hostingis such a technology and can be a great help in debugging boards with no    IOfacilities other than a    JTAGor another debugging port available.  
  When developing firmware for an embedded board,    println!-style (or    printf-style for more    C-affine readers) debugging can be immensely useful, a quick-fix that can save a lot of time that would otherwise be spent setting breakpoints or single-stepping through a program. Being able to output text does require IO ports of some sort though, be it USB, networking or another connection to the    MCUthat the code is being run on. But the mere presence of these ports is not enough, without drivers they cannot be used, creating a classic chicken-and-egg problem when implementing said drivers.  
  There is a way around the issue: During bare-metal development, uploading new program code is often handled through a targets debugging facilities, which should include a way to set breakpoints, halt the CPU and explore memory contents as well. These functionalities can be (and have been) twisted into a full-blown RPC mechanism, as shown below.
  Semi-hosting, step-by-step

      Semi-hostingrefers to making some of the    host's (i.e. the computer running the debugger) functionality available to the    target, the MCU being debugged, through the debugger itself.  
  
       
  • The target executes a      breakpointinstruction with a special tag.   
  • The debugging-software on the host is      notifiedof the breakpoint.   
  • Information inside the      first two registersindicates which procedure should be called and points to a structure with arguments.   
  • The debugger uses its memory-reading capabilities to      retrieve the argumentsand passes these on to the host's procedure.   
  • The target's CPU is unhalted by the debugger and      execution continues.  
  ARMv6 and ARMv7

  The exact process is instruction set specific, for example ARMv6 and ARMv7 use a    bkptinstruction, while some other ARM instruction sets use an    svc(    supervisor command) instruction. This article will assume an ARM Cortex-M series MCU, which is using the ARMv7 style breakpoints. Additionally, instead of using any of the commercial debugging software solutions,    gdbwill be used as the debugger. We can now implement the process step-by-step:  
  Halting the CPU

  A simple    bkpt 0xABinline assembly instruction is enough to halt the CPU (see the    inline assembly introductionfor help on the    asm!-macro):  
  1. asm!("bkpt 0xAB");
复制代码
The parameter    0xABis a magic-number taken from the    official documentation, it does not have any effect on the target, neither is it passed along when the CPU halts. Instead, the debugger is expected to find the current instruction by reading the program counter and looking it up inside the binary, then to check which value is passed. If it is    0xAB, the breakpoint is interpreted as a semi-hosting call.  
  We can now try this in gdb: A remote-debugger has been started and this is what happens when the MCU executes the    bkptinstruction:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)
复制代码
Verifying the program counter is indeed at    0x80102e4:  
  1. (gdb) p/x $pc
  2. $1 = 0x80102e4
复制代码
Checking the disassembly, we know that all the information we need is readily available. Note that the Thumb-Instruction set uses 2-byte instructions instead of 4:
  1. (gdb) disassemble 0x80102e4,+2
  2. Dump of assembler code from 0x80102e4 to 0x80102e6:
  3. => 0x080102e4 <...>:bkpt  0x00ab
  4. End of assembler dump.
复制代码
Implementing the    SVC_WRITEcall  

  The          SVC_WRITE    call is specified by the ARM compiler toolchain to write arbitrary data to a file descriptor on the host. The general calling convention for any semi-hosting call is as follows:  
  
       
  •       r0must contain the number indicating the type of call.   
  •       r1is a pointer to a struct containing arguments for the call.   
  • Each call has its own struct format.   
  • A call can return either a single 32-bit integer or an address, both of which are stored in      r0afterwards.  
  We can implement a generic SVC-calling function first:
  1. unsafe fn call_svc(num: usize, addr: *const ()) -> usize {
  2.     // allocate stack space for the possible result
  3.     let result: usize;
  4.     // move type and argument into registers r0 and r1, then trigger
  5.     // breakpoint 0xAB. afterwards, save a potential return value in r0
  6.     asm!("mov r0,$1\n\t\
  7.           mov r1,$2\n\t\
  8.           bkpt 0xAB\n\t\
  9.           mov $0,r0"
  10.         : "=ri"(result)
  11.         : "ri"(num), "ri"(addr)
  12.         : "r0", "r1"
  13.         : "volatile"
  14.        );
  15.     // return result (== r0)
  16.     result
  17. }
复制代码
The    "volatile"option indicates that the code has side-effects and should not be by removed by optimizations. The whole function is marked    unsafe, because we are dereferencing the    addrpointer, albeit the host does our dirty work.  
  To implement    SVC_WRITE, whose ID is    0x05, we also need a parameter struct (pointed to by    addr):  
  1. #[repr(C)]
  2. struct SvcWriteCall {
  3.     // the file descriptor on the host
  4.     fd: usize,
  5.     // pointer to data to write
  6.     addr: *const u8,
  7.     // length of data to write
  8.     len: usize,
  9. }
复制代码
We implement the    SYS_WRITEfunction by placing the argument structure on the stack, then passing a pointer to it to    call_svc.  
  1. const SYS_WRITE: usize = 0x05;
  2. /// Semi-hosting: `SYS_WRITE`. Writes `data` to file descriptor `fd`
  3. /// on the host. Returns `0` on success or number of unwritten bytes
  4. /// otherwise.
  5. fn svc_sys_write(fd: usize, data: &[u8]) -> usize {
  6.     let args = SvcWriteCall {
  7.         fd: fd,
  8.         addr: data.as_ptr(),
  9.         len: data.len(),
  10.     };
  11.     unsafe { call_svc(SYS_WRITE,
  12.                       &args as *const SvcWriteCall as *const ()) }
  13. }
复制代码
The function is safe because all the parameters passed to    call_svcare constant values or valid pointers; ensuring that the host's software does not have any bugs that corrupt our memory is outside the scope of our application.  
  Calling    svc_sys_writeinside    main():  
  1. // fd 2 is stderr:
  2. svc_sys_write(2, b"Hello from Rust.\n");
复制代码
Running the code again causes a    SIGTRAPin gdb:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
复制代码
Inspecting    r0and    r1  
  1. (gdb) p/x $r0
  2. $1 = 0x5
  3. (gdb) p/x $r1
  4. $2 = 0x2002ffc8
复制代码
confirms that    r0has the correct value of    0x05, while    r1points us to    0x2002ffc8, which should be a three word structure:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)0
复制代码
The first field is our file-descriptor    2. The second is the address of the string to be printed, note that it is not inside the RAM area (    0x200xxxxxx), but pointing to the flash memory (    0x080xxxxx). The string constant is read directly from the binary! The third field denotes the    17characters.  
  We can now print the string:
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)1
复制代码
Scripting the debugger

  After finishing the target's code, the next step is implementing a little script for the debugger to make things easier. Gdb supports Python scripting, so we will start with a few lines of Python to retrieve the current stack frame and    inferior, gdb's term for the target, instance. Then, it is time to decode the assembly instruction and check if it is a    bkpt:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)2
复制代码
Afterwards, we check the immediate we saved using the regular expression and compare it to    0xAB:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)3
复制代码
Finally, we retrieve the register contents of    r0and    r1and call the appropriate handler:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)4
复制代码
We will later combine all the code into a single class.
  Handling    SYS_WRITE  

  The    handle_writemethod needs to be implemented as well:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)5
复制代码
Even if it is only intended to be executed during debugging using a closed system, access to arbitrary file descriptors or unchecked length reads should make the security-conscious hair on the back of your neck stand-up; for this reason we check all arguments for sanity and limit what is written to four megabytes.
  Once we are sure our arguments are good, we can progress to read the string and print it:
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)6
复制代码
An automatic continue is triggered as well if desired.
  The final class

  All the code gets combined into a    SemiHostHelperclass:  
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)7
复制代码
Now it is time to load it and test it in gdb:
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)8
复制代码
Setting up hooks

  Once the script runs correctly, we can write a start-up script for the project to make the script automatically run on each breakpoint:
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. 0x080102e4 in hello_embed_rs::_rust_start::hfb6a6b3dc95a15dd ()
  3. (gdb)9
复制代码
The    catch signal SIGTRAPcreates a    catchpoint. A catchpoint functions like a breakpoint but triggers on signals instead, like the    SIGTRAPcaused by the CPU breakpoint. The following    commandssection defines the commands to be executed whenever the catchpoint triggers.  
  After passing the start-up script to gdb on start using the    -xoption, gdb will automatically handle the semi-hosting breakpoints and continue execution thereafter.  
  Concluding remarks

  Semi-hosting is another alternative to other IO methods that does not require any hardware except the likely already present debugging port. It is quite slow in comparison though and completely halts execution, so it is not suitable for high-volume or production logging. Another drawback is that if the breakpoints are not handled, execution will simply stay paused. It can, however, be invaluable when debugging the IO facilities themselves.
  This article showed how one of the original semi-hosting functions defined by the ARM compiler collection can be implemented, but there is no hard rule declaring these the only possible conventions. When not going for compatibility with other systems, creating smaller and safer alternatives with restricted functionality may be a viable option as well.
友荐云推荐




上一篇:Oracle puts out 253 fixes and a request to please apply patches NOW!
下一篇:So I tried Yarn, yet another new JavaScript tool
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

胡瑞 发表于 2016-10-20 15:57:46
我最恨别人用鼠标指着我的头.
回复 支持 反对

使用道具 举报

翠桃 发表于 2016-10-21 06:15:20
酷辣虫人气好旺!
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表