WOW!!
終於來到第五天了!! 經過上篇的簡介,相信對OpenOCD的運作有基本的了解,
本篇將主要的目光放在OpenOCD軟體的架構上,並針對常使用的Helper APIs做個簡單的介紹!
.
|-- 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有以下幾個:
底下主要分成Types、Command、Logging三個部分來講解!!
本節主要介紹,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:
當然也提供value 轉 buffer:
OpenOCD提供一些APIs方便開發者自行開發(註冊)新的Command和對應的Command Handler!
然後如同C裡面常用的argc/argv一樣,也提供對應的APIs可以使用!
不知道argc/argv的話,可以參考stackoverflow - What does int argc, char *argv[] mean?
先來看一個簡單的範例,以下程式碼摘自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主要可以分成以下幾個部分所構成:
hello
,表示使用者可以在GDB中使用monitor hello
來呼叫OpenOCD使用這個Commandhelp
的時候,會印出對應的訊息help
的時候另外針對mode做一個詳細的說明,一個Command可以在以下這三種情況中使用:
而COMMAND_REGISTRATION_DONE的定義如下:
#define COMMAND_REGISTRATION_DONE { .name = NULL, .chain = NULL }
這東西比較特別一點,假如說想要進階的設定Command,
比方說我們希望使用者在使用foo
的時候,後面再加上bar
、baz
和flag
,
分別表示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可以串接起來!!
最後這邊呢,要來簡單介紹一下一個Command Handler的寫法!!
基本的架構如下:
COMMAND_HANDLER(<Handler的名稱>)
{
// Do something!!
return ERROR_OK或是ERROR_FAILED;
}
好了,講完了XD 就這樣很簡單!
不過要注意就是<Handler的名稱>必須要同Command Registration時所指定的.handler
!
再來是假如Command需要帶參數進來的部分,OpenOCD提供以下幾個好用的APIs:
以上面# 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中!!
這部分比較簡單,簡單介紹一下OpenOCD Log的機制和可使用的APIs
在OpenOCD中,分別定義了以下幾種Log的Level:
實作細節(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,
};
為了方便開發者使用,OpenOCD提供以下幾種APIs可以在程式中紀錄Log!
使用方法,如同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;
}
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);
}
巴拉巴拉巴拉 打了超多字,應該破萬吧!?
本篇簡單的分析了一下OpenOCD的系統架構,並簡單介紹幾個比較常用的Helper APIs、
與實作的細節等等! 下篇將會是第二篇的Lab,簡單的在OpenOCD中加上一個Command!!一起加入Debugger的世界吧