前偏簡單的介紹了Debug Module所提供的功能以及Target狀態控制...等等,
今天就要來探討一下Debug Module中的核心功能"Abstract Commands"和"Program Buffer"
Program Buffer存在的主要目的就是可以在Target上,讓Debugger可以撰寫一些小程式,
執行任意的指令!
另外,根據Spec.中的定義,Program Buffer最後一道指令必須是"ebreak"或是"c.ebreak"之外,其餘的指令皆可任意在Program Buffer上執行,不分16-bits或是32-bits的指令皆可!
以下舉幾個簡單的例子和實作分別說明Program Buffer的用處!
先看一個簡單的Read GPR $x1的例子(64-bits的平台)
sd $x1, 0x98(zero)
fence
ebreak
簡單的三行指令就可以把$x1搬到0x98的位置存放起來!
然後只要從那邊讀出資料,就可以知道$x1的內容!
實作方式,請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number)
{
struct riscv_program program;
riscv_program_init(&program, target);
riscv_addr_t output = riscv_program_alloc_d(&program);
riscv_program_write_ram(&program, output + 4, 0);
riscv_program_write_ram(&program, output, 0);
assert(GDB_REGNO_XPR0 == 0);
if (number <= GDB_REGNO_XPR31) {
riscv_program_sx(&program, number, output); ///譯註: 裡面再去區分需要sd(64-bits) / sw(32-bits)
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
riscv_program_fsx(&program, number, output);
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
LOG_DEBUG("reading CSR index=0x%03x", number - GDB_REGNO_CSR0);
enum gdb_regno temp = riscv_program_gettemp(&program);
riscv_program_csrr(&program, temp, number);
riscv_program_sx(&program, temp, output);
} else {
LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
abort();
}
int exec_out = riscv_program_exec(&program, target); ///譯註:裡面會補上fence和ebreak,並執行Program Buffer!
if (exec_out != ERROR_OK) {
riscv013_clear_abstract_error(target);
return ERROR_FAIL;
}
*value = 0;
*value |= ((uint64_t)(riscv_program_read_ram(&program, output + 4))) << 32;
*value |= riscv_program_read_ram(&program, output);
LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
number, *value);
return ERROR_OK;
}
同上一節的Read Registers,我們考慮以下範例:
ld x1, 0x98(zero)
fence
ebreak
簡單的三行指令就可以把0x98中的資料搬到$x1的位置存放起來!
實作方式,請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int register_write_direct(struct target *target, unsigned number,
uint64_t value)
{
LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
number, value);
struct riscv_program program;
riscv_program_init(&program, target);
riscv_addr_t input = riscv_program_alloc_d(&program);
riscv_program_write_ram(&program, input + 4, value >> 32);
riscv_program_write_ram(&program, input, value);
assert(GDB_REGNO_XPR0 == 0);
if (number <= GDB_REGNO_XPR31) {
riscv_program_lx(&program, number, input); ///譯註: 裡面再去區分需要ld(64-bits) / lw(32-bits)
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
riscv_program_flx(&program, number, input);
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
enum gdb_regno temp = riscv_program_gettemp(&program);
riscv_program_lx(&program, temp, input);
riscv_program_csrw(&program, temp, number);
} else {
LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
abort();
}
int exec_out = riscv_program_exec(&program, target);
if (exec_out != ERROR_OK) {
riscv013_clear_abstract_error(target);
return ERROR_FAIL;
}
return ERROR_OK;
}
基本上流程同Read Registers
先看一個簡單的Read GPR $x1的例子(64-bits的平台)
addi s0,s0,4
lw s1,0(s0)
sw s1,0x9c(zero)
fence unknown,unknown
ebreak
假設$s0中負責存目標的Address,$s1中負責存放Data!
以上程式的流程如下:
每執行一次這個小程式,就可以讀出一筆Data!
假如需要連續讀取複數個Memory,是不是很方便啊!?
以下是實作的方式,由於內容有點複雜,有稍微修改一下,請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int read_memory(struct target *target, target_addr_t address,
uint32_t size, uint32_t count, uint8_t *buffer)
{
....省略
struct riscv_program program;
riscv_program_init(&program, target);
riscv_addr_t r_data = riscv_program_alloc_w(&program);
riscv_addr_t r_addr = riscv_program_alloc_x(&program);
riscv_program_fence(&program);
riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
switch (size) {
case 1:
riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
break;
case 2:
riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
break;
case 4:
riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
break;
default:
LOG_ERROR("Unsupported size: %d", size);
return ERROR_FAIL;
}
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
riscv_program_sw(&program, GDB_REGNO_S1, r_data);
riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
/* The first round through the program's execution we use the regular
* program execution mechanism. */
switch (riscv_xlen(target)) {
case 64:
riscv_program_write_ram(&program, r_addr + 4, ((riscv_addr_t) address) >> 32);
case 32:
riscv_program_write_ram(&program, r_addr, (riscv_addr_t) address);
break;
default:
LOG_ERROR("unknown XLEN %d", riscv_xlen(target));
return ERROR_FAIL;
}
if (riscv_program_exec(&program, target) != ERROR_OK) {
....省略錯誤處理的部分
}
int d_data = (r_data - riscv_debug_buffer_addr(target)) / 4;
int d_addr = (r_addr - riscv_debug_buffer_addr(target)) / 4;
riscv_addr_t cur_addr = riscv_read_debug_buffer_x(target, d_addr);
riscv_addr_t fin_addr = address + (count * size);
while (cur_addr < fin_addr) {
...基本上就是重複以上流程,直到讀取完畢為止!
}
....省略
中間過程落落長的,有興趣的讀者可以自行研究!
基本上同上一節的Read Memory,我們考慮以下範例:
lw s1,0x9c(zero)
sw s1,0(s0)
addi s0,s0,4
fence
ebreak
一樣假設$s0中負責存目標的Address,$s1中負責存放Data!
以上程式的流程如下:
以下是實作的方式,由於內容有點複雜,一樣只節錄部分內容,
請參考以下內容(src/target/riscv/riscv-013.c的古早版本):
static int write_memory(struct target *target, target_addr_t address,
uint32_t size, uint32_t count, const uint8_t *buffer)
{
....省略
struct riscv_program program;
riscv_program_init(&program, target);
riscv_addr_t r_data = riscv_program_alloc_w(&program);
riscv_addr_t r_addr = riscv_program_alloc_x(&program);
riscv_program_fence(&program);
riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
riscv_program_lw(&program, GDB_REGNO_S1, r_data);
switch (size) {
case 1:
riscv_program_sbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
break;
case 2:
riscv_program_shr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
break;
case 4:
riscv_program_swr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
break;
default:
LOG_ERROR("Unsupported size: %d", size);
return ERROR_FAIL;
}
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
....省略
if (riscv_program_exec(&program, target) != ERROR_OK) {
....省略錯誤處理的部分
}
/* The rest of this program is designed to be fast so it reads various
* DMI registers directly. */
int d_data = (r_data - riscv_debug_buffer_addr(target)) / 4;
int d_addr = (r_addr - riscv_debug_buffer_addr(target)) / 4;
riscv_addr_t cur_addr = 0xbadbeef;
riscv_addr_t fin_addr = address + (count * size);
LOG_DEBUG("writing until final address 0x%016" PRIx64, fin_addr);
while ((cur_addr = riscv_read_debug_buffer_x(target, d_addr)) < fin_addr) {
...基本上就是重複以上流程,直到讀取完畢為止!
}
....省略
}
終於講完Program Buffer基本的操作!
以整體架構上來看,Program Buffer基本上就是萬能的,又可以在上面執行任意指令,
那又為啥需要提供Abstract Commands的功能!?
換個角度想一下,假如今天硬體能夠對特定功能把它抽象化,讓它們從一道道的指令,
變成一個預先定義好的Register,然後透過對這個Register讀寫的操作來執行動作,
是不是就能夠加速Debugger得到資料!!! (有沒有很厲害的設計!
以上節的例子來說,就是把Access(包含Read/Write) Register,
直接變成一道指令的話,那我們是不是可以省掉大量Program Buffer操作的時間!
首先先來定義一下這個指令的格式:
---引用自RISC-V External Debug Support 0.13
這邊講解一下每個Field的意義:
這個Abstract Command的主要功能是用在Access(包含Read/Write) Register、Programm Buffer上
首先,一樣是先來看一下這個指令的格式:
---引用自RISC-V External Debug Support 0.13
---引用自RISC-V External Debug Support 0.13
因此,從上面的組合中,可以將這個Abstract Command提供的功能拆成以下幾個:
再過來就是實作的部分啦,請參考以下內容(src/target/riscv/riscv-013.c)
static int register_read_abstract(struct target *target, uint64_t *value,
uint32_t number, unsigned size)
{
RISCV013_INFO(info);
/// 譯註: 目前這版只支援Access基本的CSRs
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
!info->abstract_read_fpr_supported)
return ERROR_FAIL;
if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
!info->abstract_read_csr_supported)
return ERROR_FAIL;
uint32_t command = access_register_command(number, size,
AC_ACCESS_REGISTER_TRANSFER);
int result = execute_abstract_command(target, command);
if (result != ERROR_OK) {
if (info->cmderr == CMDERR_NOT_SUPPORTED) {
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
info->abstract_read_fpr_supported = false;
LOG_INFO("Disabling abstract command reads from FPRs.");
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
info->abstract_read_csr_supported = false;
LOG_INFO("Disabling abstract command reads from CSRs.");
}
}
return result;
}
if (value)
*value = read_abstract_arg(target, 0);
return ERROR_OK;
}
Register Read的部分也類似,請參考以下內容(src/target/riscv/riscv-013.c)
static int register_write_abstract(struct target *target, uint32_t number,
uint64_t value, unsigned size)
{
RISCV013_INFO(info);
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
!info->abstract_write_fpr_supported)
return ERROR_FAIL;
if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
!info->abstract_write_csr_supported)
return ERROR_FAIL;
uint32_t command = access_register_command(number, size,
AC_ACCESS_REGISTER_TRANSFER |
AC_ACCESS_REGISTER_WRITE);
if (write_abstract_arg(target, 0, value) != ERROR_OK) {
return ERROR_FAIL;
}
int result = execute_abstract_command(target, command);
if (result != ERROR_OK) {
if (info->cmderr == CMDERR_NOT_SUPPORTED) {
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
info->abstract_write_fpr_supported = false;
LOG_INFO("Disabling abstract command writes to FPRs.");
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
info->abstract_write_csr_supported = false;
LOG_INFO("Disabling abstract command writes to CSRs.");
}
}
return result;
}
return ERROR_OK;
}
首先,一樣是先來看一下這個指令的格式:
---引用自RISC-V External Debug Support 0.13
沒什麼好說的,cmdtype這邊固定為1!
主要提供的動作如下:
當Debugger無法在Hart的狀態是Runing下讀取資料時,可以利用Quick Access短暫的暫停Hart,然後執行預先寫好的Program Buffer取得所需的資料,在自動去Resume,避免長時間的將Hart停下!
不過由於目前這版還沒看到有使用到,這邊姑且只是翻譯一下Spec.的內容!
以上就是我覺得Debug Module精華的部分!
另外明天的主題將會深入介紹Debug Module中常用到的Registers,
應該比較繁瑣一些~~~XD