iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
0

0. 前言

冬至到了~~~ 開始覺得有點冷了XD

前篇的Lab,應該算蠻淺顯易懂的吧!?
當作簡單介紹一下OpenOCD、Telnet、GDB操作的部分,順便簡單介紹一下OpenOCD Config file的使用!
本篇將深入介紹Config file中所使用到以及相關的Commands,並開始介紹相關的程式碼,
其他較進階的Commands會在往後的文章中慢慢提到!
  
  
  

1. Command Overview

這邊挑選一些OpenOCD常用的Command,並主要分成以下幾種,
而後面的幾個小節中,將一一介紹常用Command的使用

  • Setup (Server & Debug Adapter Configuration)
  • TAP Declaration
  • CPU Configuration
  • Other Gernal Commands
      
      
      

2. Setup (Server & Debug Adapter Configuration)

本節主要是介紹基本環境設定Command,讓OpenOCD能夠順利地完成初始化,
直到整個Debug Adapter設定完成為止
  
  

2.1 Server Configuration

這邊主要是設定一些TCP/IP port number 相關的Command
  

2.1.1 Command: gdb_port [number]

設定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;
}

  

2.1.1 Command: telnet_port [number]

幾本上同上方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;
}

  
  

2.2 Debug Adapter Configuration

這邊主要是針對Debug Adapter去做設定,有些更為底層的設定會留待後續文章中介紹!
一般而言,OpenOCD的開發者針對有支援的Adapter,已經寫好預設的Config,
就如同上篇Lab所提到的一樣,直接拿來用即可!!
以下會針對較為通用的Commands來做說明
  

2.2.1 Command: interface <name>

這邊主要是設定啟動Debug Adapter的Driver,以下是目前支援的Driver

Supported Interfaces

  • altera-usb-blaster
  • altera-usb-blaster2
  • arm-jtag-ew
  • at91rm9200
  • busblaster
  • buspirate
  • calao-usb-a9260
  • chameleon
  • cmsis-dap
  • digilent-hs1
  • dummy
  • estick
  • ftdi
  • jlink
  • jtagkey-tiny
  • jtag_vpi
  • nds32-aice
  • opendous
  • openjtag
  • osbdm
  • parport
  • parport_dlc5
  • raspberrypi-native
  • rlink
  • signalyzer-h2
  • signalyzer-h4
  • stlink-v1
  • stlink-v2-1
  • stlink-v2
  • sysfsgpio-raspberrypi
  • ti-icdi
  • turtelizer2
  • ulink
  • usb-jtag
  • usbprog
  • vsllink
    --- 以上內容摘自OpenOCD網站[http://www.openocd.net/]
    絕不承認上面這段也是拿來塞篇幅的

詳細支援的內容可以在 src/jtag/interfaces.c中找到 (如果需要新增Adapter支援的話,記得在這邊註冊)
  

2.2.2 Command: adapter_khz <kHz>

這個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相關的設定!!
  
  
  

3. TAP Declaration

TAPs全名為Test Access Ports,為JTAG中的核心部分,
而OpenOCD在連結Target的時候,必須要知道相關的設定才能夠正確的連接!

而TAPs的作用,在OpenOCD Developer's Guide中有簡單的解釋如下:
`

  • Debug Target A CPU TAP can be used as a GDB debug target.
  • Flash Programming Some chips program the flash directly via JTAG. Others do it indirectly, making a CPU do it.
  • Program Download Using the same CPU support GDB uses, you can initialize a DRAM controller, download code to DRAM, and then start running that code.
  • Boundary Scan Most chips support boundary scan, which helps test for board assembly problems like solder bridges and missing connections.

---摘自OpenOCD Developer's Guide中的10 TAP Declaration
`

簡單的來說就是做為各個Target上的存取界面,包含JTAG串接的訊號介面、JTAG狀態機和相關指令/資料暫存器等等,
其主要功能常用作Debug CPU Target、Flash燒錄、程式載入以及Boundary Scan測試。
  
  

3.1 Command: jtag newtap <chipname> <tapname> [configparams]

主要用來定義新的JTAG TAPs,例如以下範例:

set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0xOOXX

由於<chipname>會經常被使用到,因此通常會用一個變數來指定,就像上例中的set _CHIPNAME riscv一樣。
再來是<tapname>的部分,OpenOCD定義以下類型的TAPs

  • bs: boundary scan
  • cpu: 一般CPU主要用這個設定
  • etb: 給ARM的Embedded Trace Buffer(ETB)用
  • flash: 當需要使用Flash的TAP時
  • jrc: For JTAG route controller---摘自OpenOCD Developer's Guide中的10 TAP Declaration 沒用過XD
  • tap: 通常是用在FPGA、CPLD上
  • unknownN: 用在你真的不知道這是啥的話

然後後面可以接上多個參數來設定這個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;
}

  
  

3.2 Command: scan_chain

這個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
  
  
  

4. CPU Configuration

上面是針對連接Target所需要設定JTAG的部分,比較繁瑣一些,需要對JTAG有深入的了解!
而本節所設定的部分,比較針對Target的設定、狀態及Event控制相關!
  
  

4.1 Command: target create <target_name> <type> [configparams]...

類似#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>來表示,必須存在的設定!!
  
  

4.2 Command: <$target_name> configure [configparams]...

上面設定好<target_name>後,接下來就是詳細設定這個Target的細節部分!
常用的[configparams]分成以下幾種:

  • -endian (big|little): 用來指定CPU屬於big endian或是little endian

例如以下範例:

$_TARGETNAME configure -endian $_ENDIAN 

OpenOCD在進階使用的時候,可以利用target上一小部分的空間運作小小的program來加速debug或是其他功能,
這部分會留待往後Flash programming時候才會詳細講解,這邊先介紹常用的設定:

  • -work-area-size: 標明可以使用的空間大小,單位bytes
  • -work-area-phys <address>: 當沒有MMU時,空間的實體位置(physical address)
  • -work-area-backup (0|1): 由於OpenOCD使用到這空間的時候會覆蓋掉原本上面的Data,這邊可選擇是否需要先備份起來(會拖慢速度)!

例如以下範例:

$_TARGETNAME configure -work-area-phys 0 -work-area-size 0x10000 -work-area-backup 1

  
  

4.3 Target Event使用

OpenOCD充許在某些事件發生後,執行預先Hook好的那些指令,
例如以下的情況:

  • 當GDB連線上時,先設定好一些Server相關的設定
  • Target Reset前/後,執行預先定義好的設定
  • ...

以下是節錄目前常用的Event:

  • debug-halted: 當Target進入Debug Mode時候,比方說踩到breakpoint時
  • debug-resumed: 當GDB將Target進入Resume時
  • gdb-attach: GDB連線時
  • gdb-detach: GDB斷線後
  • halted: Target進入halted後
  • reset-assert: 假如需要特別方式來做SRST時
  • reset-deassert-post: SRST釋放後
  • ...很多種類的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來除錯!
  
  
  

5. Other Gernal Commands

經過以上設定後,相信應該都能夠順利地了解OpenOCD如何從Init開始,一路經過Adapter設定、
JTAG TAPs設定,直到最後Target設定的部分!!

而本節主要是介紹一些平常在操作OpenOCD上,
  
  

5.1 Target相關的Gernal Commands

這邊主要是延續上面#2 #3 #4的部分,介紹一些常用的Commands
  

5.1.1 Command: interface_list

查看目前OpenOCD所支援的Adapter清單,如果使用的是我在Day 02所提供的Script的話,
應該會出現以下的畫面:

> interface_list
The following debug interfaces are available:
1: ftdi
2: hla

因為我提供的Script中,其他的Adapter Support都被我關啦XDDDD
  

5.1.2 Command: poll [on|off]

OpenOCD會定期去Polling(輪詢) Target的狀況,這邊可以開或關閉這項功能!
  

5.1.3 Command: targets [name]

前篇Labs有提到了,這邊不重複說明!!
打太多字,有點累了!
  

5.1.4 Command: target types

一樣## 4.1 Command: target create有介紹到了,不重複贅述!
  
  

5.2 Server相關的Gernal Commands

這邊簡單介紹一下Server常用的幾個Commands
  

5.2.1 Command: exit

這個簡單,就是關掉Telnet的連線!!
  

5.2.2 Command: help [string]

應該不用解釋了吧XD!?
  

5.2.3 Command: sleep msec [busy]

等待一定時間後,再繼續做下去!
通常用在上面所介紹## 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
                 */
        }
}

  

5.2.4 Command: shutdown [error]

這邊比較特別一點,可以關掉所有OpenOCD所支援的Server和斷開所有連線!
比方說: Telnet、GDB、其他自訂的Server
  

5.2.5 Command: debug_level [n]

上篇Lab中的#2.2.2 OpenOCD的使用,有稍微介紹到當OpenOCD執行的時候,
可以利用Level數字-3~3,從LOG_LVL_SILENT(-3)~LOG_LVL_DEBUG(3)來調整Log紀錄的項目,
當然,同樣的,這邊也可以進行設定!!
  

5.2.6 Command: log_output [filename]

同樣的,上篇Lab中的#2.2.2 OpenOCD的使用,有稍微介紹到當OpenOCD執行的時候,
可以設定Log file的路徑,將預設會直接打印在OpenOCD的console上的Log,導向檔案中,方便除錯!
這邊也同樣是後面加上Log的路徑!!
  
  

5.3 Target State相關的Gernal Commands

這邊介紹的Commands主要跟Target狀態控制有關!

5.3.1 Command: halt [ms]

讓Target進入halt的狀態,OpenOCD會等待[ms]或是預設的5秒,
如果設定0ms的話,OpenOCD則不會進行等待!
  

5.3.2 Command: resume [address]

讓Target在指定的位置做Resume,並從那個位置開始繼續執行下去!
如果不指定Address的話,則從Halt當下的位置開始執行下去!
  

5.3.3 Command: step [address]

讓Target進行Single-step,這邊後續文章會深入分析!
  

5.3.4 Command: reset run

顧名思義,就是先Reset後再讓Target進入Free-Run的狀態!!
  

5.3.5 Command: reset halt

顧名思義,就是先Reset後再讓Target進入Halt的狀態!!
  

5.3.6 Command: reset

這邊就是沒加任何參數的Reset,預設行為同reset run
  
  
  

99. 結語

巴拉巴拉巴拉 介紹了這麼多Commands後,相信對OpenOCD的操作有更深入的了解了~!
下一篇開始,將會深入OpenOCD Source Code的世界中!
一起加入Debugger的世界吧
  
  
  

參考資料

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

上一篇
Day 03: [Lab] 簡單操作OpenOCD
下一篇
Day 05: OpenOCD 軟體架構
系列文
系統架構秘辛:了解RISC-V 架構底層除錯器的秘密!30

尚未有邦友留言

立即登入留言