EJTAG:吸引黑客2


在我以前的EJTAG出版物中:对黑客Black Swift 的吸引力:使用EJTAG时,考虑了使用EJTAG的最简单方法-加载到RAM中并启动用户程序。但是,EJTAG的功能不限于此。该出版物描述了如何使用openocd和GDB免费软件工具使用EJTAG来组织简单的代码调试。

一位读者的来信提示我写这篇出版物,他要求我保持匿名并向我寻求帮助-基于AR9344的设备无法启动(在U-boot初始化阶段挂起)-我如何找出EJTAG的问题所在?
由于我手头上没有基于AR9344的设备,但是基于相关AR9331芯片的Black Swift Pro板却成为了叙述的焦点。我认为AR9344所做的更改并不重要。
让我们
继续问题的陈述:有一块Black Swift Pro板,我们通过EJTAG使用openocd连接到它,并使处理器进入停止模式。
要求顺序执行数十条处理器指令,在每条指令执行后停止,并在必要时检查RAM,引导ROM,外围控制器寄存器或处理器寄存器的内容。
要解决该问题,我至少看到两种方法:
  • 简单-仅使用openocd-openocd已经具有执行所需操作的基本功能。只需要能够使用它即可;
  • 复杂-使用openocd + GDB包-用户将控制通过GDB执行处理器指令的过程,而openocd会将GDB请求转换为EJTAG命令。

现在更详细地考虑两种解决方案。
, Black Swift: EJTAG.


1: openocd


那些阅读过我以前有关EJTAG的出版物的人应该记住,openocd在其中似乎是一个哑脚本执行器(配置文件),它似乎以批处理方式工作,并且不提供用户交互。但是,事实并非如此。实际上,在运行openocd软件时,可以使用命令行界面“询问”它以执行命令。要访问命令行界面,openocd将启动telnet服务器。
缺省情况下,Telnet服务器使用TCP端口4444,如有必要,可使用选项更改TCP端口号telnet_port(请参见下面的示例)。
让我们尝试使用openocd跟踪Black Swift板的引导程序。
配置文件示例black-swift-trace.cfg 对于openocd,这将迫使telnet服务器的openocd使用端口4455:
 来源[寻找介面/ ftdi / tumpa.cfg]
 
 adapter_khz 6000
 
 来源[找到black-swift.cfg]
 
 telnet_port 4455
 
 在里面
 停止

以root身份运行openocd 0.9.0如下所示:
 #openocd -f black-swift-trace.cfg
 打开片上调试器0.9.0(2015-05-28-17:08)
 根据GNU GPL v2许可
 有关错误报告,请阅读
         http://openocd.org/doc/doxygen/bugs.html
 没有分开
 适配器速度:6000 kHz
 信息:自动选择第一个可用的会话传输“ jtag”。要覆盖,请使用“运输选择<运输>”。
 错误:找不到设备
 错误:无法使用vid 0403,pid 8a98,描述'*'和串行'*'打开ftdi设备
 信息:时钟速度6000 kHz
 信息:JTAG接头:ar9331.cpu接头/找到的设备:0x00000001(MFG:0x000,零件:0x0000,ver:0x0)
 目标状态:已停止
 由于调试请求,目标在MIPS32模式下暂停,pc:0xbfc00000
 目标状态:已停止
 由于单步操作,目标在MIPS32模式下停止,pc:0xbfc00404

现在我们可以打开另一个终端窗口,并使用以下程序连接到openocd telnet服务器telnet
 $ telnet本地主机4455
 尝试:: 1 ...
 尝试127.0.0.1 ...
 连接到本地主机。
 转义字符为'^]'。
 打开片上调试器
 >

使用该命令很容易获得所有openocd命令的列表help
为了逐步执行处理器指令,step以下命令对我们有用
 步骤[地址]
       在寄存器指定的地址执行一条指令
       命令计数器(PC)。如果指定了地址参数,则
       该指令将从地址​​地址开始执行。

在控制台中逐步执行处理器指令如下所示:
 >步骤0xbfc00400
 目标状态:已停止
 由于单步操作,目标在MIPS32模式下停止,pc:0xbfc00404
 >步骤
 目标状态:已停止
 由于单步操作,目标在MIPS32模式下停止,pc:0xbfc00408
 >步骤
 目标状态:已停止
 由于单步操作,目标在MIPS32模式下停止,pc:0xbfc0040c

以下openocd命令也可能有用:
 reg [(寄存器编号|寄存器名称)[(值|'力')]]
       读取或写入处理器寄存器值。
       不带参数调用reg将导致所有寄存器的输出。
       如果使用'force'参数,则强制
       从处理器中减去寄存器(而不是发出缓存)
       值)。
 
 mwb ['phys'] address value [count]
          address ,     value.
          phys,   address —  ,
          — .
          count    address  
           count,    
         value.
 
 mwh ['phys'] address value [count]
         mwb,     16- .
 
 mww ['phys'] address value [count]
         mwb,     32- .
 
 mdb ['phys'] address [count]
               address.
          phys,   address —  ,
          — .
          count        
       数组位于地址地址长度的计数字节。
 
 mdh ['phys']地址[count]
       该命令类似于mdb,但是读取的是16位字而不是字节。
 
 mdw ['phys']地址[count]
       该命令类似于mdb,但是读取的是32位字而不是字节。

如您所见,不幸的是,尽管有一个具有ARM体系结构的处理器反汇编器,但最新的(在撰写本文时)openocd 0.9.0版本无法反汇编具有MIPS体系结构的处理器的指令
由于缺少反汇编程序,因此直接使用openocd逐步执行处理器指令并不十分方便。如果使用GDB,则可以提高舒适度。

解决方案2:使用openocd + GDB捆绑包


在openocd + GDB捆绑包中,角色分配如下:用户与GDB进行通信,这提供了一个方便的调试接口,从控制指令执行的机制中抽象出来,openocd承担了根据GDB的指令直接控制处理器的任务。
与openocd相比,使用GDB通过EJTAG控制MIPS处理器上指令的执行具有多个优点:
  • 如上所述,GDB内置了用于MIPS架构的反汇编程序;
  • 可以使用源中的调试信息;例如,如果您正在调试自己的C程序,则GDB将能够显示当前正在执行的C代码行,并精确地详细说明程序变量的状态,而不是带有神秘地址的存储单元;
  • openocd GDB GDB Remote Serial Protocol; qemu , , — GDB;
  • , GDB TAB.

应当记住,GDB使用高级概念运行,并且openocd被迫与这样的设备一起工作,并且并非总是可以使用EJTAG有效地实现GDB Wishlist。
例如,用户指示GDB在指定地址处设置断点,该指令转到openocd,但是对于MIPS处理器,openocd至少有两种方法来设置断点:
  • sdbbp, , openocd sdbbp , sdbbp , . software breakpoint. , .
  • . . , hardware breakpoint, .

尽管在GDB远程串行协议中,硬件断点和软件断点之间是有区别的(请参见协议描述中的程序包z和z0 ),并且GDB提供了选择断点类型的适当选项,但可能会发现在特定处理器上使用点有限制一种或另一种类型的细分。因此,openocd有一个选项gdb_breakpoint_override,允许您强制采用上述两种组织断点的方法之一。
为了连接GDB调试器,openocd实现了一个GDB服务器,该服务器默认使用TCP端口3333。如有必要,可以使用option更改TCP端口号gdb_port
要连接到控制MIPS处理器的openocd GDB服务器,我们需要具有MIPS支持的特殊版本的GDB。我假设读者使用Sourcery CodeBench软件包中的mips-linux-gnu-gdb,使用运行Debian Linux的基于x86 / amd64的计算机来运行openocd和GDB。关于如何安装此软件包的信息,请在此处撰写,在编写这些行时,最后一个是2015年5月发布的Sourcery CodeBench mips-2015.05-18版本,您可以使用此链接在此处下载存档
让我们尝试在实践中使用openocd + GDB捆绑包。运行openocd:
 #openocd -f运行u-boot_mod-trace.cfg \
 > -c“ gdb_breakpoint_override hard” -c“步骤0xbfc00400”

openocd的最后两个命令将提供以下内容:
  • 不管GDB想象在那里,都将使用硬件断点(地址0xbfc0xxxx对应于ROM,因此软件断点将不起作用);
  • 将从地址0xbfc00400执行一条指令,此后处理器将再次停止。

打开另一个终端窗口并启动GDB:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb
 GNU gdb(Sourcery CodeBench Lite 2015.05-18)7.7.50.20140217-cvs
 版权所有(C)2014自由软件基金会,Inc.
 许可证GPLv3 +:GNU GPL版本3或更高版本<http://gnu.org/licenses/gpl.html>
 这是免费软件:您可以自由更改和重新分发它。
 在法律允许的范围内,没有任何担保。输入“显示复制”
 和“显示保修”了解详情。
 该GDB被配置为“ --host = i686-pc-linux-gnu --target = mips-linux-gnu”。
 键入“显示配置”以获取配置详细信息。
 有关错误报告的说明,请参阅:
 <https://sourcery.mentor.com/GNUToolchain/>。
 在线找到GDB手册和其他文档资源:
 <http://www.gnu.org/software/gdb/documentation/>。
 要获得帮助,请键入“帮助”。
 输入“ apropos word”以搜索与“ word”相关的命令。
 (gdb)

现在,我们解释GDB所使用的处理器的类型,要求反汇编该处理器的下一条可执行指令,最后连接到openocd GDB服务器:
 (GDB)设置体系结构MIPS:ISA32R2
 假定目标体系结构是mips:isa32r2
 (gdb)设置endian大
 假定目标为大端
 (gdb)将disassemble-next-line设置为on
 (GDB)目标遥控器:3333
 远程调试使用:3333
 0xbfc00404 in ?? ()
 => 0xbfc00404:40 80 08 00 mtc0零,c0_random

为了在GDB中执行一条处理器指令,使用了一条命令stepi让我们尝试遵循处理器中的一些指令,不要被GDB关于缺少调试信息(找不到函数的开始)的警告所迷惑,在这种情况下没有地方可以获取此信息。
 (gdb)stepi
 警告:GDB在0xbfc00408处找不到该函数的开始。
 
     GDB在0xbfc00408处找不到该函数的开始
 因此无法确定该函数的堆栈框架的大小。
 这意味着GDB可能无法访问该堆栈帧,或者
 它下面的框架。
     此问题很可能是由无效的程序计数器或
 堆栈指针。
     但是,如果您认为GDB应该简单地向后搜索
 从0xbfc00408开始,代码看起来像a的开头
 功能,您可以使用`set来扩大搜索范围
 启发式围栏后命令。
 0xbfc00408 in ?? ()
 => 0xbfc00408:40 80 10 00 mtc0零,c0_entrylo0
 (gdb)stepi
 警告:GDB在0xbfc0040c处找不到该函数的开始。
 0xbfc0040c在?? ()
 => 0xbfc0040c:40 80 18 00 mtc0零,c0_entrylo1
 (gdb)stepi
 警告:GDB在0xbfc00410处找不到该函数的开始。
 0xbfc00410 in ?? ()
 => 0xbfc00410:40 80 20 00 mtc0零,c0_context
 (gdb)

现在读取0xbfc00408处的32位字:
 (gdb)p / x * 0xbfc00408
 $ 1 = 0x40801000

要打印处理器寄存器的状态,请使用以下命令info registers
 (GDB)信息寄存器
           在v0处为零v1 a0 a1 a2 a3
  R0 00000000 37c688e2 22b15a00 28252198 0c12d319 4193c014 84e49102 06193640
             t0 t1 t2 t3 t4 t5 t6 t7
  R8 00000002 9f003bc0 92061301 1201c163 31d004a0 92944911 ac031248 b806001c
             s0 s1 s2 s3 s4 s5 s6 s7
  R16 8bc81985 402da011 c94d2454 88d5a554 81808e0d cc445151 4401a826 50020402
             t8 t9 k0 k1 gp sp s8 ra
  R24 01c06b30 01000000 10000004 fffffffe 9f003bc0 54854eab 329d626b bfc004b4
         状态lo hi badvaddr原因pc
       00400004 00244309 b9ca872c ed6a1f00 60808350 bfc00410
           fcsr冷杉
       00000000 00000000

GDB是具有大量命令和选项的高级工具。为了更熟悉GDB,我请读者阅读GDB官方文档

初始化AR9331 RAM控制器


让我们看看GDB如何解决一个特定的问题:通过在Black Swift板上跟踪U-boot执行,我们将在RAM控制器的寄存器中标识一系列条目,从而导致其初始化。如果我们想绕过U-boot使用openocd在Black Swift上运行程序,则检测到这样的序列非常有用。同样,在为Black Swift创建备用引导程序时,此初始化序列很有用。
运行openocd(就像上一节一样):
 #openocd -f运行u-boot_mod-trace.cfg \
 > -c“ gdb_breakpoint_override hard” -c“步骤0xbfc00400”

要运行GDB,请编写一个脚本bs-u-boot-trace-gdb.conf
 设置体系结构的技巧:isa32r2
 设置大端
 设置反汇编下一行
 
 目标遥控器:3333
 
 取消分页
 
 设置日志文件bs_gdb.log
 设置登录
 
 而$ pc!=(void(*)())0x9f002ab0
         斯蒂皮
         信息寄存器
 结束
 
 分离
 
 退出

与上一节中的示例相比,该脚本使GDB将输出复制到文件中bs_gdb.log,并禁用分页(用户确认页面翻页),然后开始循环执行处理器指令并在每条指令之后显示处理器寄存器的状态。当PC寄存器(以下指令的地址寄存器)达到值0x9f002ab0时,GDB与openocd断开连接并停止工作。因此,在GDB的末尾,将创建bs_gdb.log一个具有处理器指令执行完整轨迹的文件
GDB的启动如下:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb -x bs-u-boot-trace-gdb.conf

注意:该脚本bs-u-boot-trace-gdb.conf很可能在板上电后不会立即运行,因为u-boot_mod将另外重置AR9331的反神秘错误,这将导致脚本停止执行。在这种情况下,请停止openocd和GDB,然后再次运行openocd和GDB。

现在,一切都变得很小了-您需要从文件中选择bs_gdb.log所有写入指令sw(存储字,即写入32位值)。AR9331存储器控制器的寄存器为32位,因此可以安全地忽略存储系列中的其他指令。
由于反汇编器仅生成指令的参数寄存器的名称sw
 => 0xbfc004ec:ad f9 00 00 sw t9,0(t7)

但不是它们的值,仅从file中bs_gdb.log选择包含sw语句的所有行是不够的为了确定使用sw将哪个值写入哪个地址,必须对该文件进行bs_gdb.log其他处理。可以使用parse_gdb_output.pl脚本完成处理:
 #!/usr/bin/perl -w
 
 my %r;
 
 foreach $i (qw(zero at v0 v1 a0 a1 a2 a3 t0 t1 t2 t3 t4 t5 t6 t7
        s0 s1 s2 s3 s4 s5 s6 s7 t8 t9 k0 k1 gp sp s8 ra)) {
    $r{$i} = "none";
 }
 
 sub parse_reg($)
 {
    $_ = $_[0];
    if (/^ R/) {
        my @fields = split m'\s+';
        my $f = 2;
        my @rgs;
 
        @rgs = qw(zero at v0 v1 a0 a1 a2 a3) if (/^ R0/);
        @rgs = qw(t0 t1 t2 t3 t4 t5 t6 t7) if (/^ R8/);
        @rgs = qw(s0 s1 s2 s3 s4 s5 s6 s7) if (/^ R1/);
        @rgs = qw(t8 t9 k0 k1 gp sp s8 ra) if (/^ R2/);
 
        foreach $i (@rgs) {
            $r{$i} = $fields[$f];
            $f = $f + 1;
        }
    }
 }
 
 while (<>) {
    if (/^=>([^s]*)\tsw\t([^,]*),(\d+)\(([^)]*)\)/) {
        my $rs = $2;
        my $offset = $3;
        my $rd = $4;
 
        parse_reg(<>);
        parse_reg(<>);
        parse_reg(<>);
        parse_reg(<>);
 
        print("$1    sw $rs={0x$r{$rs}}, $offset($rd={0x$r{$rd}})\n");
    }
 }


启动parse_gdb_output.pl如下:
$ grep“ ^ = \ | ^ R” bs_gdb.log | ./parse_gdb_output.pl

这是输出的摘要parse_gdb_output.pl(标记' <<< PLL'和' <<< DDR'稍后手动输入):
 ...
  0x9f002700:ad cf 00 00 sw t7 = {0x00dbd860},0(t6 = {0xb8116248})
  0x9f00271c:ad f9 00 00 sw t9 = {0x000fffff},0(t7 = {0xb800009c})
  0x9f0027a0:ad f9 00 00 sw t9 = {0x00018004},0(t7 = {0xb8050008})<<< PLL
  0x9f0027dc:   ad f9 00 00    sw t9={0x00000352}, 0(t7={0xb8050004}) <<<
  0x9f002840:   ad f9 00 00    sw t9={0x40818000}, 0(t7={0xb8050000}) <<<
  0x9f002898:   ad f9 00 00    sw t9={0x001003e8}, 0(t7={0xb8050010}) <<<
  0x9f0028f4:   ad f9 00 00    sw t9={0x00818000}, 0(t7={0xb8050000}) <<<
  0x9f002970:   ad cf 00 00    sw t7={0x00800000}, 0(t6={0xb8116248})
 ...
  0x9f002994:   ad cf 00 00    sw t7={0x40800700}, 0(t6={0xb8116248})
  0x9f002a54:   ad f9 00 00    sw t9={0x00008000}, 0(t7={0xb8050008})
  0x9f00309c:   af 38 00 00    sw t8={0x7fbc8cd0}, 0(t9={0xb8000000}) <<< DDR
  0x9f0030b0:   af 38 00 00    sw t8={0x9dd0e6a8}, 0(t9={0xb8000004}) <<<
  0x9f0030dc:   af 38 00 00    sw t8={0x00000a59}, 0(t9={0xb800008c}) <<<
  0x9f0030ec:af 38 00 00 sw t8 = {0x00000008},0(t9 = {0xb8000010})<<<
 ...

由于时钟信号发生器(PLL)的寄存器的地址和DDR类型的内存控制器的寄存器的地址是已知的,因此很容易弄清楚应该向哪个地址写入哪些数字才能正确初始化RAM控制器。

摘要


如您所见,使用openocd和GDB通过JTAG进行调试并不困难,并且所描述的工作方法不仅适用于AR9331,而且经过一些调整甚至适用于具有不同架构的处理器,openocd和GDB均对此提供了支持。

Source: https://habr.com/ru/post/zh-CN383505/


All Articles