在多核芯片调试的时候,不少人会把“把调试窗口切换到某个核心上去看它里面寄存器的状态”和“只让这一个核心运行、把其他核心都停住”这两个动作当成一回事。要用TRACE32同时调试多个核心,得先把两个基本的事情理清楚:一个是怎么在不同核心之间来回切换,让调试器显示我们想看的那个核心的上下文;另一个是怎么控制多个核心同步地启动和停下来。而要弄清楚这两点,最要紧的一步就是先判断当前的系统处在哪种模式下,是对称多处理(SMP)模式,还是非对称多处理(AMP)模式。TRACE32本身是能够同时挂载好几颗核心一起来调试的,你可以让所有核心同步动作,也可以只盯着其中某一个核心的状态,前提是把下面这些设置和命令弄对了。
一、多核调试时怎样切换到想查看的那颗核心
在TRACE32里面,所谓的切核,大多数时候指的是改变当前调试窗口所关注的那个核心对象。一切换之后,寄存器窗口、反汇编窗口、调用栈窗口还有变量查看窗口里显示出来的内容,都会变成新核心里边的东西,但是这并不表示硬件电路上就只有这个核心在跑,其他的核心都被停下来了。
1、首先得保证芯片上的那些核心已经被正确地连进了调试系统
在启动脚本里面,要检查好几条命令的配置写对了没有,比如SYStem.CPU用来指定CPU类型,SYStem.CONFIG.CoreNumber告诉系统一共有几个核,还有CORE.ASSIGN命令则是专门用来把具体的物理核心给挑选出来,然后把它们纳入到一个SMP的调试框架里面去。当CORE.ASSIGN执行完之后,TRACE32就会把这些选好的物理核心当成逻辑核心来统一管理,后面我们要做的切核操作也就全部在这些逻辑核心之间进行了。
2、核心连好以后,就可以用CORE.SELECT命令把视图切换到指定的那个逻辑核心上
比如在SMP模式下,用CORE.SELECT切到逻辑核0,屏幕上就会显示出这个核当时的寄存器值、程序计数器指到了哪个地址这些关键信息。这里有一个很容易出错的地方要记住:CORE.SELECT只是改变了调试器自己的显示焦点,它并不会让其他核心停止运行,也不能保证只有当前这个核心在动。在Lauterbach的社区回复里面也清楚地解释过,CORE.SELECT的作用就是把调试器的观察窗口挪到选中的那颗核心上面去,跟核心的运行控制是两码事。
3、切完核心之后
最好马上打开寄存器窗口、任务列表、调用栈和程序计数器这些地方,亲眼确认一下它们显示的内容是不是跟着变成了新核心对应的数据。在多核调试的环境下,不能简单地只看源码窗口里高亮了哪一行,因为不同核心可能会刚好在同一个函数里面停下来,或者在地址很接近的代码段上,光靠位置判断很容易看错,把几个窗口对照着检查才会更加保险。
二、怎么才能让多个核心的启动和停止同步起来
控制多个核心同步运行的关键,就在于Go和Break这类动作是只影响到当前显示的那个核心,还是会同时作用到所有已经分配进调试系统里的核心。在SMP模式下面,一般Go命令和Break命令都是面向整个调试组里的全部核心一起生效的。
1、在SMP调试环境里,你只要执行一次Go命令,那些已经被分配好的核心就会一起跑起来;同样,当需要它们停下时,执行一次Break命令,这些核心又会一起被拉停。根据Lauterbach提供的资料来看,TRACE32确实能够支持被同一条调试会话管理的核心同步启动和停止,并且还能在一个状态窗口里面同时显示出各个核心当前是跑着还是停着,方便整体掌握情况。
2、如果有某个核心因为踩到了断点而停住,那么通常系统中的其他核心也会跟着被同步暂停,不会出现只有中招的那个核停了,其他核还在继续运行的情况。而且调试器的焦点会自动切到那个触发断点的核心上去。关于AMP和SMP的调试文档里也提到,一个核心因为断点停止时,其余核心会自动跟着停,TRACE32同时会把界面焦点移到断点命中的那个任务或者说那颗核心上面。
3、要是项目采用的是AMP模式,事情就变得不太一样了。
在AMP模式下,不同的核心很可能由不同的TRACE32图形界面实例来分别控制。AMP方案的做法,是把好几个各自独立的GUI实例整合到一个统一的多核调试系统里面,然后再通过这个系统去协调各个核心的启动和停止步调。如果需要在AMP下单独控制某一个核心,一般得确保脚本为每一颗核心都建立了独立的调试会话,而不是仅仅在一个SMP会话里面靠来回切换视图来操作,不然很难做到真正的单人单核控制。
三、多核调试出现异常时应该从哪里开始排查
在做切核操作或者同步运行控制的过程中,一旦碰上了不符合预期的现象,大多数时候并不是因为某条命令本身失效了,而是因为启动脚本里的相关配置、物理核心与逻辑核心的编号,或者调试模式的选择这几件事情之间没有对齐好。
1.仔细检查物理核心的编号和逻辑核心的编号是不是给弄混了。CORE.ASSIGN这条命令挑选的对象,是芯片上实实在在存在的物理核心;而CORE.SELECT操作的对象,是那种经过分配和整理之后的逻辑核心编号。如果把物理编号和逻辑编号混着用,就容易出现一种情况:自己以为已经切到3号核心去看东西了,可实际上当前界面里显示出来的资源根本就不是那个核心的,这个错误在刚开始做多核调试的时候出现得特别多。
2.再确认一下当前调试工作到底是跑在SMP模式下,还是跑在AMP模式下。在SMP模式下切核仅仅是在移动观察的视角,Go和Break往往还是会统一作用于所有核心;而在AMP模式下面,不同的核心可能本来就不属于同一个调试实例。如果目的是单独操纵某一个核心,那不光要用CORE.SELECT,还得回头去看看调试会话在一开始创建的时候,是不是已经按照独立核心的方式给配置好了,有没有为每个核心分别建立起互相隔离的会话通道。
3.还要检查一下断点设置的作用范围。在多核环境下,同一个内存地址可能被好几个不同的核心分别执行。在设断点的时候,心里一定要想清楚,是希望不管哪个核心跑到了这个位置都停下来,还是只盯着一个特定的核心等它触发。要是断点一触发,整个系统所有的核心都被同时拉停了,那就要结合芯片本身的调试架构和TRACE32采用的同步停止策略,去判断这种全部停住的行为到底符不符合原本的设想。
4.最后也别忘看一眼脚本里面各个初始化步骤的先后顺序。SYStem.CPU的声明、核心数量的指定、CORE.ASSIGN的分配、目标连接动作、符号文件加载,还有设置断点的操作,这几步之间最好保持一个比较清晰的、固定的前后关系。要是在写脚本的时候,先设置完了断点才去完成核心的分配,或者在核心分配结束之后又中途去重置系统状态,这类操作都很可能把整个多核调试的环境状态给搅乱,导致后续命令执行出来的效果变得难以捉摸。
总结
总结起TRACE32多核调试来,切核操作的思路可以概括成:先靠CORE.ASSIGN把需要的物理核心拉进调试系统并转成逻辑核心,再用CORE.SELECT在这些逻辑核心之间切换当前观察的视角。至于同步运行的控制,就得看清楚工作模式,SMP模式下一条Go或Break命令通常就会让全体核心一起动或者一起停,而AMP模式更依赖于多个独立调试实例之间的协同配合。遇到问题排查的时候,重点核对物理核与逻辑核的编号是不是用对了,调试模式是不是匹配,断点的作用范围是不是合理,以及脚本初始化的顺序有没有安排妥当,按照这些方向查下来,一般就能判断出故障到底是卡在视图切换上、核心控制上,还是同步策略上面。