iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 5
0
Software Development

系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!系列 第 5

Day 05: OpenOCD 軟體架構

0. 前言

WOW!!
終於來到第五天了!! 經過上篇的簡介,相信對OpenOCD的運作有基本的了解,
本篇將主要的目光放在OpenOCD軟體的架構上,並針對常使用的Helper APIs做個簡單的介紹!
  
  
  

1. 目錄結構

.
|-- flash
|   |-- nand
|   `-- nor
|-- helper
|-- jtag
|   |-- aice
|   |-- drivers
|   |   |-- OpenULINK
|   |   |   |-- include
|   |   |   `-- src
|   |   |-- libjaylink
|   |   |   |-- autom4te.cache
|   |   |   |-- build-aux
|   |   |   |-- contrib
|   |   |   |-- libjaylink
|   |   |   `-- m4
|   |   |-- usb_blaster
|   |   `-- versaloon
|   |       `-- usbtoxxx
|   |-- hla
|   |-- minidriver
|   |-- minidummy
|   `-- zy1000
|-- pld
|-- rtos
|-- server
|-- svf
|-- target
|   |-- openrisc
|   `-- riscv
|-- transport
`-- xsvf

本系列預計會用到的folder有以下幾個:

  • flash: 裡面有分成Nor Flash和Nand Flash的支援,預計會在主題4. Flash programming時提到
  • helper: 裡面放一堆OpenOCD所提供好用的APIs,會常常用到,底下幾節也會講解到
  • jtag: OpenOCD最底層,JTAG控制的支援,預計會在主題3. FTDI based Debug Adapter Hardware詳細提到
  • server: 裡面放GDB / Telnet 等等各種Server的支援,預計會在主題5. GDB中詳細分析GDB Server的支援
  • target: OpenOCD最核心的部分,對每個Target支援的程式都會分類放在這邊,預計會在主題2. RISC-V External Debug Support (Version 0.13)時,詳細講解target/riscv中的各種功能實作方式!
  • 其他比如rtos、transport等等,會等到有空閒之餘再另外補充 如果有的話
      
      
      

2. Helper APIs

底下主要分成Types、Command、Logging三個部分來講解!!
  
  

2.1 Types

本節主要介紹,src/helper/types.h中的內容!
建議參照著看比較能夠深入了解!!!

在這個檔案中,分別定義了一些常用變數的型別

#define PRIx32 "x"
#define PRId32 "d"
#define SCNx32 "x"
#define PRIi32 "i"
#define PRIu32 "u"
#define PRId8 PRId32
#define SCNx64 "llx"
#define PRIx64 "llx"

target: Add 64-bit target address support (commit 47b8cf84202bf792cf66fbfa01169e9592236b8a)之後,
又加上了一個新的支援!

變成

#define PRId32 "d"
#define PRIi32 "i"
#define PRIo32 "o"
#define PRIu32 "u"
#define PRIx32 "x"
#define PRIX32 "X"
#define SCNx32 "x"
#define PRId8 PRId32
#define SCNx64 "llx"
#define PRId64 "lld"
#define PRIi64 "lli"
#define PRIo64 "llo"
#define PRIu64 "llu"
#define PRIx64 "llx"
#define PRIX64 "llX"

又同時新增了一個target_addr_t這個型別讓OpenOCD裡面用到address的部分可以同時支援32-bits和64-bits!

以下是詳細的定義:

#if BUILD_TARGET64
typedef uint64_t target_addr_t;
#define TARGET_ADDR_MAX UINT64_MAX
#define TARGET_PRIdADDR PRId64
#define TARGET_PRIuADDR PRIu64
#define TARGET_PRIoADDR PRIo64
#define TARGET_PRIxADDR PRIx64
#define TARGET_PRIXADDR PRIX64
#else
typedef uint32_t target_addr_t;
#define TARGET_ADDR_MAX UINT32_MAX
#define TARGET_PRIdADDR PRId32
#define TARGET_PRIuADDR PRIu32
#define TARGET_PRIoADDR PRIo32
#define TARGET_PRIxADDR PRIx32
#define TARGET_PRIXADDR PRIX32
#endif

其中BUILD_TARGET64可以在configure中,使用--disable-target64將其關閉!
另外在這邊也提供一些APIs,將uint8_t的buffer作相對應的轉換!

buffer 轉 value:

  • le_to_h_u64: Little Endian的Buffer轉成uint64_t
  • be_to_h_u64: Big Endian的Buffer轉成uint64_t
  • le_to_h_u32: Little Endian的Buffer轉成uint32_t
  • be_to_h_u32: Big Endian的Buffer轉成uint32_t
  • le_to_h_u16: Little Endian的Buffer轉成uint16_t
  • be_to_h_u16: Big Endian的Buffer轉成uint16_t

當然也提供value 轉 buffer:

  • h_u64_to_le
  • h_u64_to_be
  • h_u32_to_le
  • h_u32_to_be
  • h_u16_to_le
  • h_u16_to_be
      
      

2.2 Command APIs

OpenOCD提供一些APIs方便開發者自行開發(註冊)新的Command和對應的Command Handler!
然後如同C裡面常用的argc/argv一樣,也提供對應的APIs可以使用!

不知道argc/argv的話,可以參考stackoverflow - What does int argc, char *argv[] mean?

  

2.2.1 Command Registration APIs

先來看一個簡單的範例,以下程式碼摘自src/hello.c

const struct command_registration hello_command_handlers[] = {
        {
                .name = "hello",
                .handler = handle_hello_command,
                .mode = COMMAND_ANY,
                .help = "prints a warm welcome",
                .usage = "[name]",
        },
        {
                .name = "foo",
                .mode = COMMAND_ANY,
                .help = "example command handler skeleton",
        },
        COMMAND_REGISTRATION_DONE
};

從中可以知道,一個Command主要可以分成以下幾個部分所構成:

  • name: Command的名稱,主要的部分,如範例中hello,表示使用者可以在GDB中使用monitor hello來呼叫OpenOCD使用這個Command
  • handler: 負責處理這個Command的處理函式的名稱
  • mode: 主要分成EXEC/CONFIG/ANY這三種type
  • help: 顯示說明的地方,當使用者使用help的時候,會印出對應的訊息
  • usage: 通常放可接受的參數,一樣是用在help的時候

另外針對mode做一個詳細的說明,一個Command可以在以下這三種情況中使用:

  • EXEC: 表示這個Command在OpenOCD init好之後才能夠使用
  • CONFIG: 表示這個Command主要使用在Config的階段
  • ANY: 顧名思義,就是在EXEC和CONFIG中,皆可以使用

而COMMAND_REGISTRATION_DONE的定義如下:

#define COMMAND_REGISTRATION_DONE { .name = NULL, .chain = NULL }

  

2.2.2 Command Chaining

這東西比較特別一點,假如說想要進階的設定Command,
比方說我們希望使用者在使用foo的時候,後面再加上barbazflag
分別表示Command foo bar [參數...]foo baz [參數...]foo flag [參數...]時,
則可以利用.chain的設定,將Command串接起來!

同樣以2.2.1 Command Registration APIs中的例子,延伸一下

static const struct command_registration foo_command_handlers[] = {
        {
                .name = "bar",
                .handler = &handle_foo_command,
                .mode = COMMAND_ANY,
                .usage = "address ['enable'|'disable']",
                .help = "an example command",
        },
        {
                .name = "baz",
                .handler = &handle_foo_command,
                .mode = COMMAND_ANY,
                .usage = "address ['enable'|'disable']",
                .help = "a sample command",
        },
        {
                .name = "flag",
                .handler = &handle_flag_command,
                .mode = COMMAND_ANY,
                .usage = "[on|off]",
                .help = "set a flag",
        },
        COMMAND_REGISTRATION_DONE
};

const struct command_registration hello_command_handlers[] = {
        {
                .name = "foo",
                .mode = COMMAND_ANY,
                .help = "example command handler skeleton",

                .chain = foo_command_handlers,
        },
        COMMAND_REGISTRATION_DONE
};

上半部foo_command_handlers的寫法如同2.2.1 Command Registration APIs中的教學一樣,
比較特別的地方是在"foo"這個Command中,我們可以利用.chain來指向這個陣列!
讓Command可以串接起來!!
  

2.2.3 Command Handler

最後這邊呢,要來簡單介紹一下一個Command Handler的寫法!!

基本的架構如下:

COMMAND_HANDLER(<Handler的名稱>)
{
        // Do something!!

        return ERROR_OK或是ERROR_FAILED;
}

好了,講完了XD 就這樣很簡單!
不過要注意就是<Handler的名稱>必須要同Command Registration時所指定的.handler!

再來是假如Command需要帶參數進來的部分,OpenOCD提供以下幾個好用的APIs:

  • CMD_ARGC: 就是argc
  • CMD_ARGV: 就是同argv

以上面# 2.2.1 Command Registration APIs中的例子,我們可以看到hello後面可以加上一個參數

簡單的實作細節如下:

COMMAND_HANDLER(handle_hello_command)
{
        if (CMD_ARGC > 1)
                return ERROR_COMMAND_SYNTAX_ERROR;

        if (1 == CMD_ARGC) {
                printf("Hello %s\n", CMD_ARGV[0]);
        }

        return ERROR_OK;
}

以上就是個簡單的例子!
OpenOCD另外提供COMMAND_HELPER這個APIs用來將特定Function定位為輔助的函式!
原先的Command Handler可藉由調用CALL_COMMAND_HANDLER來呼叫這個輔助函式!

我們把上面的例子做個延伸(改編自src/hello.c):

static COMMAND_HELPER(handle_hello_args, const char **sep, const char **name)
{
        if (CMD_ARGC > 1)
                return ERROR_COMMAND_SYNTAX_ERROR;
        
        if (1 == CMD_ARGC) {
                *sep = " ";
                *name = CMD_ARGV[0];
        } else
                *sep = *name = "";

        return ERROR_OK;
}

COMMAND_HANDLER(handle_hello_command)
{
        const char *sep, *name;
        int retval = CALL_COMMAND_HANDLER(handle_hello_args, &sep, &name);
        if (ERROR_OK == retval)
                printf("Greetings%s%s!\n", sep, name);
        return retval;
}

把原先判斷argc/argv的部分放到handle_hello_args中!!
  
  

2.3 Logging APIs

這部分比較簡單,簡單介紹一下OpenOCD Log的機制和可使用的APIs
  

2.3.1 Logging Level

在OpenOCD中,分別定義了以下幾種Log的Level:

  • SILENT
  • OUTPUT
  • USER
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • DEBUG_IO

實作細節(src/helper/log.c)

enum log_levels {
        LOG_LVL_SILENT = -3,
        LOG_LVL_OUTPUT = -2,
        LOG_LVL_USER = -1,
        LOG_LVL_ERROR = 0,
        LOG_LVL_WARNING = 1,
        LOG_LVL_INFO = 2,
        LOG_LVL_DEBUG = 3,
        LOG_LVL_DEBUG_IO = 4,
};

2.3.2 Logger

為了方便開發者使用,OpenOCD提供以下幾種APIs可以在程式中紀錄Log!

  • LOG_INFO
  • LOG_WARNING
  • LOG_ERROR
  • LOG_USER
  • LOG_DEBUG
  • LOG_DEBUG_IO
  • ...其他

使用方法,如同printf一般使用,只是最後面不需要加上'\n',OpenOCD會自動在後面加上斷行

例如:

COMMAND_HANDLER(handle_hello_command)
{
        LOG_DEUBG("ARGC: %d", CMD_ARGC);

        if (CMD_ARGC > 1)
                return ERROR_COMMAND_SYNTAX_ERROR;

        if (1 == CMD_ARGC) {
                printf("Hello %s\n", CMD_ARGV[0]);
        }

        return ERROR_OK;
}

2.3.3 Log Output

OpenOCD只會紀錄比目前設定中debug_level還要"小"的那些Log!
比方說設定debug_level 0(Error),則表示那些使用到"LOG_WARNING"、"LOG_INFO"、"LOG_DEBUG"的Log皆不會出現在Log中!
忘記debug_level可以參考上篇 # 5.2.5 Command: debug_level [n]

實作的細節在src/helper/log.c中

void log_printf(enum log_levels level,
        const char *file,
        unsigned line,
        const char *function,
        const char *format,
        ...)
{
        char *string;
        va_list ap;

        count++;
        if (level > debug_level)        // 編註: 這裡判斷!!
                return;

        va_start(ap, format);

        string = alloc_vprintf(format, ap);
        if (string != NULL) {
                log_puts(level, file, line, function, string);
                free(string);
        }

        va_end(ap);
}

另外如果使用debug_level 3(Debug),則OpenOCD會在Log前面自動加上Lable、Sequence number、Timestamp

類似下面的效果

Debug: 93 14 core.c:1660 adapter_khz_to_speed(): convert khz to interface specific speed value
Debug: 94 14 core.c:1663 adapter_khz_to_speed(): have interface set up
Debug: 95 14 core.c:1660 adapter_khz_to_speed(): convert khz to interface specific speed value
Debug: 96 14 core.c:1663 adapter_khz_to_speed(): have interface set up
Info : 97 14 core.c:1448 adapter_init(): clock speed 950 kHz
Debug: 98 14 openocd.c:140 handle_init_command(): Debug Adapter init complete                                                    
Debug: 99 14 command.c:143 script_debug(): command - ocd_command ocd_command type ocd_transport init
Debug: 100 14 command.c:143 script_debug(): command - ocd_transport ocd_transport init
Debug: 102 14 transport.c:239 handle_transport_init(): handle_transport_init
Debug: 103 14 hla_transport.c:152 hl_transport_init(): hl_transport_init
Debug: 104 14 hla_transport.c:169 hl_transport_init(): current transport hla_swd
Debug: 105 14 hla_interface.c:42 hl_interface_open(): hl_interface_open
Debug: 106 14 hla_layout.c:40 hl_layout_open(): hl_layout_open
Debug: 107 14 stlink_usb.c:1642 stlink_usb_open(): stlink_usb_open
Debug: 108 14 stlink_usb.c:1659 stlink_usb_open(): transport: 1 vid: 0x0483 pid: 0x3748 serial:
Info : 109 128 stlink_usb.c:563 stlink_usb_version(): STLINK v2 JTAG v27 API v2 SWIM v0 VID 0x0483 PID 0x3748
Info : 110 128 stlink_usb.c:1770 stlink_usb_open(): using stlink api v2
Debug: 111 132 stlink_usb.c:762 stlink_usb_init_mode(): MODE: 0x00
Info : 112 193 stlink_usb.c:595 stlink_usb_check_voltage(): Target voltage: 2.895283
Debug: 113 193 stlink_usb.c:817 stlink_usb_init_mode(): MODE: 0x01
Debug: 114 201 stlink_usb.c:843 stlink_usb_init_mode(): MODE: 0x02

另外要注意一點如果使用了"LOG_USER"、"LOG_WARNING"、"LOG_ERROR"這三個APIs,
則如果觸發Log的話,會將此一訊息發送(forward)給目前所有連線中的Client,
連線中的Client都將會看到這一訊息!

可以參考src/helper/log.c中的實作

static void log_puts(enum log_levels level,
        const char *file,
        int line,
        const char *function,
        const char *string)
{
        char *f;
        if (level == LOG_LVL_OUTPUT) {
                /* do not prepend any headers, just print out what we were given and return */
                fputs(string, log_output);
                fflush(log_output);
                return;
        }

        f = strrchr(file, '/');
        if (f != NULL)
                file = f + 1;

        if (strlen(string) > 0) {
                if (debug_level >= LOG_LVL_DEBUG) {
                        /* print with count and time information */
                        int64_t t = timeval_ms() - start;
#ifdef _DEBUG_FREE_SPACE_
                        struct mallinfo info;
                        info = mallinfo();
#endif
                        fprintf(log_output, "%s%d %" PRId64 " %s:%d %s()"
#ifdef _DEBUG_FREE_SPACE_
                                " %d"
#endif
                                ": %s", log_strings[level + 1], count, t, file, line, function, // 編註: timestamp以ms為單位
#ifdef _DEBUG_FREE_SPACE_
                                info.fordblks,
#endif
                                string);
                } else {
                        /* if we are using gdb through pipes then we do not want any output
                         * to the pipe otherwise we get repeated strings */
                        fprintf(log_output, "%s%s",
                                (level > LOG_LVL_USER) ? log_strings[level + 1] : "", string);
                }
        } else {
                /* Empty strings are sent to log callbacks to keep e.g. gdbserver alive, here we do
                 *nothing. */
        }

        fflush(log_output);

        // 編註: 這邊判斷如果log不為LOG_LVL_INFO等級以下的話,自動將其forward出去
        /* Never forward LOG_LVL_DEBUG, too verbose and they can be found in the log if need be */
        if (level <= LOG_LVL_INFO)
                log_forward(file, line, function, string);
}

  
  
  

99. 結語

巴拉巴拉巴拉 打了超多字,應該破萬吧!?
本篇簡單的分析了一下OpenOCD的系統架構,並簡單介紹幾個比較常用的Helper APIs、
與實作的細節等等! 下篇將會是第二篇的Lab,簡單的在OpenOCD中加上一個Command!!
一起加入Debugger的世界吧
  
  
  

參考資料

  1. OpenOCD User's Guide
  2. OpenOCD Developer's Guide

上一篇
Day 04: OpenOCD常用Commands簡介
下一篇
Day 06: [Lab] 簡簡單單新增OpenOCD Command
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
微中子
iT邦新手 4 級 ‧ 2017-12-24 00:09:41

韌體 debug 超麻煩的

我要留言

立即登入留言