My code works, I don’t know why.

國王的耳朵是驢耳朵

ARM CM4 Pratice (4): Semihosting

| Comments

Semihosting 是一種讓開發版透過除錯介面執行主機上的服務。透過semihosting,使用者可以快速驗證功能。舉例來說,在USART還沒搞定前先把訊息透過semihosting印到主機上觀看程式的行為。

本次練習使用openocd提供主機上的服務。以下是這次的練習

目錄

測試環境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.4 LTS
Release:  14.04
Codename: trusty

$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 5.4.1 20160609 (release) [ARM/embedded-5-branch revision 237715]
...

$ openocd --version
Open On-Chip Debugger 0.10.0-dev-00250-g9c37747 (2016-04-07-22:20)
...

  • SPL版本: STM32F4xx_DSP_StdPeriph_Lib_V1.6.1
  • 開發板: STM32F4 Dicovery, STM32F429-Disco

使用Semihosting

開發版

ARM官方網站有說,在ARMv6-M 和 ARMv7-M (Cortex M4是ARMv7E-M)架構中,使用break point指令並帶入參數0xab即可。詳細的使用範例會在後面說明。

以下是ARM規範的Semihosting服務

主機 (openocd)

其實非常簡單,就是啟動openocd的時候要把semihosting打開。單獨指令如下

1
arm semihosting enable

實驗:Echo

完整可以編譯的程式碼在這邊

這個實驗行為和前一個實驗很類似,就是透過semihosting的read服務主機端輸入資料,然後再透過semihosting的write服務印到螢幕上。

程式

主程式

非常簡單,唯一要說明的是在微處理器中,所有的東西都要自己來,也就是說下面的memsetstrlen都是自己寫的。

semihosting.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "my_utils.h"

#define GREETING_STR  "Hello World\n\r"
#define PROMPT_STR    "\r> "
#define LINE_MAX_CHAR (64)

int main(int argc, char **argv)
{
    char line[LINE_MAX_CHAR];

    /* Greeting */
    host_write(STDOUT, GREETING_STR, strlen(GREETING_STR));
    host_write(STDOUT, PROMPT_STR, strlen(PROMPT_STR));
    while(1) {
        memset(line, 0x00, LINE_MAX_CHAR);
        host_read(STDIN, line, LINE_MAX_CHAR);
        host_write(STDOUT, line, strlen(line));

        /* Show prompt */
        host_write(STDOUT, PROMPT_STR, strlen(PROMPT_STR));
    }

    return 0;
}

semihosting 程式

首先我們從手冊中知道read/write的semihosting對應代號,用hardcode是不道德的,所以我們使用有意義的文字代替。

1
2
3
4
enum HOST_SYSCALL {
    HOSTCALL_WRITE       = 0x05,
    HOSTCALL_READ        = 0x06,
};

接下來我們發現read/write參數的型態有整數(file descriptor) 和位址這兩種,而手冊中說明參數傳遞的方式是將參數全部放在一個連續的記憶體空間,再將該記憶體空間位址存放在R1暫存器中。也就是說我們需要

  1. 連續的空間
  2. 空間中的參數可能有多個,而且它們的型態可能都不同

要達到這樣的方式,以前上課看來的方式就是做一個union。每次使用時建立陣列、指令參數型態和值,接下來把該陣列的位址傳給semihosting就可以了。

以下是這次用的union

1
2
3
4
5
6
7
8
/* Semihost system call parameters */
union param_t
{
    int   pdInt;
    void *pdPtr;
};

typedef union param_t param;

接下來是實際的呼叫semihosting服務。就是前面提到下break point指令。你可能會問這邊怎麼沒有參數傳遞?傻孩子,這就是ABI存在的目的了。

1
2
3
4
5
6
7
8
9
10
11
12
static int host_call(enum HOST_SYSCALL action, void *arg) __attribute__ ((naked));
static int host_call(enum HOST_SYSCALL action, void *arg)
{
    int result;
    __asm__( \
      "bkpt 0xAB\n"\
      "nop\n" \
      "bx lr\n"\
        :"=r" (result) ::\
    );
    return result;
}

剩下就簡單了,設定好參數呼叫semihosting的介面收工。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
size_t host_read(int fd, void *buf, size_t count)
{
    param semi_param[3] = {
        { .pdInt = fd },
        { .pdPtr = buf },
        { .pdInt = count }
    };

    return host_call(HOSTCALL_READ, semi_param);
}

size_t host_write(int fd, const void *buf, size_t count)
{
    param semi_param[3] = {
        { .pdInt = fd },
        { .pdPtr = (void *) buf },
        { .pdInt = count }
    };

    return host_call(HOSTCALL_WRITE, semi_param);
}

Makefile

最主要的是要透過openocd提供semihosting服務。如果想要在gdb測試時打開可以參考這邊的資料。

Makefile相關描述如下:

1
2
3
4
5
6
7
run: $(OUT_DIR)/$(TARGET).bin
  openocd -f interface/stlink-v2.cfg  \
            -f target/stm32f4x.cfg      \
            -c "init"                   \
            -c "reset init"             \
            -c "arm semihosting enable" \
            -c "reset run"

執行結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ make run
openocd -f interface/stlink-v2.cfg  \
            -f target/stm32f4x.cfg      \
            -c "init"                   \
            -c "reset init"             \
            -c "arm semihosting enable" \
            -c "reset run"
Open On-Chip Debugger 0.10.0-dev-00250-g9c37747 (2016-04-07-22:20)
Licensed under GNU GPL v2
For bug reports, read
  http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 2000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Info : STLINK v2 JTAG v17 API v2 SWIM v0 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 2.883392
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
adapter speed: 2000 kHz
stm32f4x.cpu: target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x080005c4 msp: 0x20030000
adapter speed: 8000 kHz
semihosting is enabled
adapter speed: 2000 kHz
Hello World
> My test
My test
> !!!!
!!!!
>

參考資料

Comments