冬至到了~~~ 開始覺得有點冷了XD
前篇的Lab,應該算蠻淺顯易懂的吧!?
當作簡單介紹一下OpenOCD、Telnet、GDB操作的部分,順便簡單介紹一下OpenOCD Config file的使用!
本篇將深入介紹Config file中所使用到以及相關的Commands,並開始介紹相關的程式碼,
其他較進階的Commands會在往後的文章中慢慢提到!
這邊挑選一些OpenOCD常用的Command,並主要分成以下幾種,
而後面的幾個小節中,將一一介紹常用Command的使用
本節主要是介紹基本環境設定Command,讓OpenOCD能夠順利地完成初始化,
直到整個Debug Adapter設定完成為止
這邊主要是設定一些TCP/IP port number 相關的Command
設定GDB連接去OpenOCD的Port number,這是最常用到的Command之一,
不指定的話,預設號碼為3333。
值得注意的是,如果所設定的Port被占用的話,OpenOCD底層實作會直接結束.....(蠻令人...的設計...
另外,針對多重target(multi-core、multi-target)的部分,
一個target就會配上一個GDB Server,也就是說一個target就會有一個對應的GDB Port!
相關的程式碼可以在src/server/gdb_server.c 找到
static int gdb_target_add_one(struct target *target)
{
if (strcmp(gdb_port, "disabled") == 0) {
LOG_INFO("gdb port disabled");
return ERROR_OK;
}
/* one gdb instance per smp list */
if ((target->smp) && (target->gdb_service))
return ERROR_OK;
int retval = gdb_target_start(target, gdb_port_next);
if (retval == ERROR_OK) { //#譯註: 如果發現不成功,跳到最後,回傳ERROR_FAIL
long portnumber;
/* If we can parse the port number
* then we increment the port number for the next target.
*/
char *end;
portnumber = strtol(gdb_port_next, &end, 0);
if (!*end) {
if (parse_long(gdb_port_next, &portnumber) == ERROR_OK) {
free(gdb_port_next);
if (portnumber) {
gdb_port_next = alloc_printf("%d", portnumber+1); //#譯註: 如果發現成功,則將Port number+1,
//並持續將剩下的target放入
} else {
/* Don't increment if gdb_port is 0, since we're just
* trying to allocate an unused port. */
gdb_port_next = alloc_printf("0");
}
}
}
}
return retval;
}
如果需要讓OpenOCD持續將剩下的Target加入,或許可以考慮以下實作方式
static int gdb_target_add_one(struct target *target)
{
if (strcmp(gdb_port, "disabled") == 0) {
LOG_INFO("gdb port disabled");
return ERROR_OK;
}
/* one gdb instance per smp list */
if ((target->smp) && (target->gdb_service))
return ERROR_OK;
while(1) {
int retval = gdb_target_start(target, gdb_port_next);
long portnumber = strtol(gdb_port_next, &end, 0);
if (!*end) {
if (parse_long(gdb_port_next, &portnumber) == ERROR_OK) {
free((void *)gdb_port_next);
gdb_port_next = alloc_printf("%d", portnumber+1);
}
}
if (retval == ERROR_OK) {
printf("The core #%d listens on %d.\n", (int)target->target_number, (int)portnumber);
break;
}
else if ((portnumber+1) > 65535) {
LOG_ERROR("gdb port number fail");
printf("gdb port number fail!!\n");
fflush(stdout);
return ERROR_FAIL;
}
}
return retval;
}
幾本上同上方gdb_port,比較需要注意的是,一個OpenOCD只會有一個Telnet Server,
但不同於GDB一個Port僅能容許一個connetion,Telent可同時接受多個Connection
詳細可以參考src/server/telnet_server.c中的實作
int telnet_init(char *banner)
{
if (strcmp(telnet_port, "disabled") == 0) {
LOG_INFO("telnet server disabled");
return ERROR_OK;
}
struct telnet_service *telnet_service =
malloc(sizeof(struct telnet_service));
if (!telnet_service) {
LOG_ERROR("Failed to allocate telnet service.");
return ERROR_FAIL;
}
telnet_service->banner = banner;
int ret = add_service("telnet", telnet_port, CONNECTION_LIMIT_UNLIMITED, //#譯註: 這邊沒有限制連入數量!
telnet_new_connection, telnet_input, telnet_connection_closed,
telnet_service);
if (ret != ERROR_OK) {
free(telnet_service);
return ret;
}
return ERROR_OK;
}
這邊主要是針對Debug Adapter去做設定,有些更為底層的設定會留待後續文章中介紹!
一般而言,OpenOCD的開發者針對有支援的Adapter,已經寫好預設的Config,
就如同上篇Lab所提到的一樣,直接拿來用即可!!
以下會針對較為通用的Commands來做說明
這邊主要是設定啟動Debug Adapter的Driver,以下是目前支援的Driver
Supported Interfaces
詳細支援的內容可以在 src/jtag/interfaces.c中找到 (如果需要新增Adapter支援的話,記得在這邊註冊)
這個Command主要是用來設定Adapter連結Target的時候,所使用的會高(快)速度,
以kHz為單位,所以如果設定adapter_khz 3000
,就表示目前JTAG Clock為3MHz,
有些Adapter支援自動Scan Clock的功能,比如說Andes的AICE-mini、AICE-MCU...等等 繼續廣告,
詳細資料請參考Adapter相關的說明!
另外值得一提的是,如果adapter_khz後面帶0的話,就表示啟動RTCK,
可以參考(FAQ RTCK)[http://openocd.org/doc/html/FAQ.html#faqrtck]中有更詳細的說明
詳細的Source Code可以在src/jtag/core.c中找到
int adapter_init(struct command_context *cmd_ctx)
{
...中間省略...
int requested_khz = jtag_get_speed_khz();
int actual_khz = requested_khz;
int jtag_speed_var = 0;
retval = jtag_get_speed(&jtag_speed_var);
if (retval != ERROR_OK)
return retval;
retval = jtag->speed(jtag_speed_var);
if (retval != ERROR_OK)
return retval;
retval = jtag_get_speed_readable(&actual_khz);
if (ERROR_OK != retval)
LOG_INFO("adapter-specific clock speed value %d", jtag_speed_var);
else if (actual_khz) {
/* Adaptive clocking -- JTAG-specific */
if ((CLOCK_MODE_RCLK == clock_mode)
|| ((CLOCK_MODE_KHZ == clock_mode) && !requested_khz)) {
LOG_INFO("RCLK (adaptive clock speed) not supported - fallback to %d kHz"
, actual_khz);
} else
LOG_INFO("clock speed %d kHz", actual_khz);
} else
LOG_INFO("RCLK (adaptive clock speed)");
return ERROR_OK;
}
...中間省略...
static int jtag_set_speed(int speed)
{
jtag_speed = speed;
/* this command can be called during CONFIG,
* in which case jtag isn't initialized */
return jtag ? jtag->speed(speed) : ERROR_OK;
}
int jtag_config_khz(unsigned khz)
{
LOG_DEBUG("handle jtag khz");
clock_mode = CLOCK_MODE_KHZ;
int speed = 0;
int retval = adapter_khz_to_speed(khz, &speed);
return (ERROR_OK != retval) ? retval : jtag_set_speed(speed);
}
int jtag_config_rclk(unsigned fallback_speed_khz)
{
LOG_DEBUG("handle jtag rclk");
clock_mode = CLOCK_MODE_RCLK;
rclk_fallback_speed_khz = fallback_speed_khz;
int speed = 0;
int retval = jtag_rclk_to_speed(fallback_speed_khz, &speed);
return (ERROR_OK != retval) ? retval : jtag_set_speed(speed);
}
以上到這邊,大至基本的連線設定都完成了,接下來的章節部分,
會開始進入Target相關的設定!!
TAPs全名為Test Access Ports,為JTAG中的核心部分,
而OpenOCD在連結Target的時候,必須要知道相關的設定才能夠正確的連接!
而TAPs的作用,在OpenOCD Developer's Guide中有簡單的解釋如下:
`
---摘自OpenOCD Developer's Guide中的10 TAP Declaration
`
簡單的來說就是做為各個Target上的存取界面,包含JTAG串接的訊號介面、JTAG狀態機和相關指令/資料暫存器等等,
其主要功能常用作Debug CPU Target、Flash燒錄、程式載入以及Boundary Scan測試。
主要用來定義新的JTAG TAPs,例如以下範例:
set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xOOXX
由於<chipname>會經常被使用到,因此通常會用一個變數來指定,就像上例中的set _CHIPNAME riscv
一樣。
再來是<tapname>的部分,OpenOCD定義以下類型的TAPs
For JTAG route controller
---摘自OpenOCD Developer's Guide中的10 TAP Declaration 沒用過XD然後後面可以接上多個參數來設定這個TAPs,比方說
-irlen <NUMBER\>: 用來設定JTAG中instruction register的bit數,通常為4 or 5 bits,看硬體怎麼設計!
-expected-id <NUMBER\>: non-zero的32-bit IDCODE,用來讓OpenOCD判斷所連結的JTAG是否正確。
值得一提的地方在於,如果JTAG IDCODE全為0或是全為1的話,通常表示有問題,
相關檢查的Source Code在src/jtag/core.c中:
static bool jtag_examine_chain_check(uint8_t *idcodes, unsigned count)
{
uint8_t zero_check = 0x0;
uint8_t one_check = 0xff;
for (unsigned i = 0; i < count * 4; i++) {
zero_check |= idcodes[i];
one_check &= idcodes[i];
}
/* if there wasn't a single non-zero bit or if all bits were one,
* the scan is not valid. We wrote a mix of both values; either
*
* - There's a hardware issue (almost certainly):
* + all-zeroes can mean a target stuck in JTAG reset
* + all-ones tends to mean no target
* - The scan chain is WAY longer than we can handle, *AND* either
* + there are several hundreds of TAPs in bypass, or
* + at least a few dozen TAPs all have an all-ones IDCODE
*/
if (zero_check == 0x00 || one_check == 0xff) {
LOG_ERROR("JTAG scan chain interrogation failed: all %s",
(zero_check == 0x00) ? "zeroes" : "ones");
LOG_ERROR("Check JTAG interface, timings, target power, etc.");
return false;
}
return true;
}
這個Command比較簡單,就是列出OpenOCD目前所設定的TAP內容,包含Tap名稱、狀態、IDCODE、IR Length等等資訊,
例如:
TapName Enabled IdCode Expected IrLen IrCap IrMask
-- ------------------ ------- ---------- ---------- ----- ----- ------
0 omap5912.dsp Y 0x03df1d81 0x03df1d81 38 0x01 0x03
1 omap5912.arm Y 0x0692602f 0x0692602f 4 0x01 0x0f
2 omap5912.unknown Y 0x00000000 0x00000000 8 0x01 0x03
---摘自OpenOCD Developer's Guide中的10 TAP Declaration
上面是針對連接Target所需要設定JTAG的部分,比較繁瑣一些,需要對JTAG有深入的了解!
而本節所設定的部分,比較針對Target的設定、狀態及Event控制相關!
類似#3.1 Command: jtag newtap 所提到的一樣,不過這邊主要是用來定義TAP上的Target,例如以下範例:
set _CHIPNAME riscv
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
另外由於<target_name>由於會經常被使用到,因此通常會用一個變數來指定,就像上例中的set _TARGETNAME $_CHIPNAME.cpu
一樣。
最方便的使用方式是同TAP定義的一樣,比如上篇Lab所使用到的STM32-F429(tcl/target/stm32f4x.cfg):
set _CHIPNAME stm32f4x
set _TARGETNAME $_CHIPNAME.cpu
swj_newdap $_CHIPNAME cpu -irlen 4 ...後面省略
target create $_TARGETNAME cortex_m -chain-position $_TARGETNAME
再來是<type>的部分,OpenOCD裡面支援多種Target,詳細所支援的Target可以參考官方網站!
另外提醒一下,type名稱是由Source Code中所定義的,例如: stm32f4x在OpenOCD要使用"cortex_m",
NDS32的CPU依照指令集分成"nds32_v2", "nds32_v3" ... 各種不同的type 偷偷推廣一下!
也可以利用上篇文章中的Telent,使用target types
來查詢目前OpenOCD所支援的Target
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> target types
arm7tdmi arm9tdmi arm920t arm720t arm966e arm946e arm926ejs fa526 feroceon dragonite xscale cortex_m cortex_a
cortex_r4 arm11 ls1_sap mips_m4k avr dsp563xx dsp5680xx testee avr32_ap7k hla_target nds32_v2 nds32_v3 nds32_v3m
or1k quark_x10xx quark_d20xx riscv aarch64
[configparams]在這邊通常只需要定義這個Target屬於哪個Tap中,並用-chain-position <name>
來表示,必須存在的設定!!
上面設定好<target_name>後,接下來就是詳細設定這個Target的細節部分!
常用的[configparams]分成以下幾種:
例如以下範例:
$_TARGETNAME configure -endian $_ENDIAN
OpenOCD在進階使用的時候,可以利用target上一小部分的空間運作小小的program來加速debug或是其他功能,
這部分會留待往後Flash programming時候才會詳細講解,這邊先介紹常用的設定:
例如以下範例:
$_TARGETNAME configure -work-area-phys 0 -work-area-size 0x10000 -work-area-backup 1
OpenOCD充許在某些事件發生後,執行預先Hook好的那些指令,
例如以下的情況:
以下是節錄目前常用的Event:
例如以下範例(摘自stm32f4x.cfg):
$_TARGETNAME configure -event reset-start {
# Reduce speed since CPU speed will slow down to 16MHz with the reset
adapter_khz 2000
}
** 在Reset之前,先將Adapter的速度降至2 MHz **
以上介紹到這邊,OpenOCD應該要能夠順利地打起來,並正確地連接上板子!!
如果哪邊有設定錯誤,通常OpenOCD的Log中會用Error:
表示,
可以先看看說明,對照Error message來除錯!
經過以上設定後,相信應該都能夠順利地了解OpenOCD如何從Init開始,一路經過Adapter設定、
JTAG TAPs設定,直到最後Target設定的部分!!
而本節主要是介紹一些平常在操作OpenOCD上,
這邊主要是延續上面#2 #3 #4的部分,介紹一些常用的Commands
查看目前OpenOCD所支援的Adapter清單,如果使用的是我在Day 02所提供的Script的話,
應該會出現以下的畫面:
> interface_list
The following debug interfaces are available:
1: ftdi
2: hla
因為我提供的Script中,其他的Adapter Support都被我關啦XDDDD
OpenOCD會定期去Polling(輪詢) Target的狀況,這邊可以開或關閉這項功能!
前篇Labs有提到了,這邊不重複說明!!打太多字,有點累了!
一樣## 4.1 Command: target create有介紹到了,不重複贅述!
這邊簡單介紹一下Server常用的幾個Commands
這個簡單,就是關掉Telnet的連線!!
應該不用解釋了吧XD!?
等待一定時間後,再繼續做下去!
通常用在上面所介紹## 4.3 Target Event中使用,用來控制時序!
如果使用[busy]的話,OpenOCD會使用busy-wait的方式,而不是Sleep
詳細可以參考src/helper/command.c和src/helper/log.c中的實作
# src/helper/command.c中
COMMAND_HANDLER(handle_sleep_command)
{
bool busy = false;
if (CMD_ARGC == 2) {
if (strcmp(CMD_ARGV[1], "busy") == 0)
busy = true;
else
return ERROR_COMMAND_SYNTAX_ERROR;
} else if (CMD_ARGC < 1 || CMD_ARGC > 2)
return ERROR_COMMAND_SYNTAX_ERROR;
unsigned long duration = 0;
int retval = parse_ulong(CMD_ARGV[0], &duration);
if (ERROR_OK != retval)
return retval;
if (!busy) {
int64_t then = timeval_ms();
while (timeval_ms() - then < (int64_t)duration) {
target_call_timer_callbacks_now();
usleep(1000); <-- 使用Sleep的方式
}
} else
busy_sleep(duration); <-- 使用busy-wait的方式
return ERROR_OK;
}
# src/helper/log.c中
void busy_sleep(uint64_t ms)
{
uint64_t then = timeval_ms();
while (timeval_ms() - then < ms) {
/*
* busy wait
*/
}
}
這邊比較特別一點,可以關掉所有OpenOCD所支援的Server和斷開所有連線!
比方說: Telnet、GDB、其他自訂的Server
上篇Lab中的#2.2.2 OpenOCD的使用,有稍微介紹到當OpenOCD執行的時候,
可以利用Level數字-3~3,從LOG_LVL_SILENT(-3)~LOG_LVL_DEBUG(3)來調整Log紀錄的項目,
當然,同樣的,這邊也可以進行設定!!
同樣的,上篇Lab中的#2.2.2 OpenOCD的使用,有稍微介紹到當OpenOCD執行的時候,
可以設定Log file的路徑,將預設會直接打印在OpenOCD的console上的Log,導向檔案中,方便除錯!
這邊也同樣是後面加上Log的路徑!!
這邊介紹的Commands主要跟Target狀態控制有關!
讓Target進入halt的狀態,OpenOCD會等待[ms]或是預設的5秒,
如果設定0ms的話,OpenOCD則不會進行等待!
讓Target在指定的位置做Resume,並從那個位置開始繼續執行下去!
如果不指定Address的話,則從Halt當下的位置開始執行下去!
讓Target進行Single-step,這邊後續文章會深入分析!
顧名思義,就是先Reset後再讓Target進入Free-Run的狀態!!
顧名思義,就是先Reset後再讓Target進入Halt的狀態!!
這邊就是沒加任何參數的Reset,預設行為同reset run
巴拉巴拉巴拉 介紹了這麼多Commands後,相信對OpenOCD的操作有更深入的了解了~!
下一篇開始,將會深入OpenOCD Source Code的世界中!一起加入Debugger的世界吧