参考博文:和https://www.sohu.com/a/130338404_505803
1 Coresight
WHY:为什么需要
首先从命名上,可以知之一二:就是看看core在干嘛呗!
当然这只是小编的口语。根据ARM的官方,CoreSight主要实现两个功能:Debug和Trace。
对于搞嵌入式的工程师而言并不陌生,也就是对于内核的调试和跟踪功能。
在早期可以通过片外仪器来测量处理器调试过程中的数据和指令流,而后SoC的大范围应用,片内Cache的使用也变得非常广泛。这就使得片外仪器就无法监测了,因此处理器厂商就提供硬件片上的Trace功能,通过专有的硬件非入侵地实时记录程序执行路径和数据读写信息,然后压缩成Trace数据流,通过专用的数据通道和输出端口传输至调试主机(开头提到的600的厉害之处主要就是针对这个位置的创新)。调试主机中的开发工具解压缩这些Trace数据流,恢复程序运行信息后,就可以进行调试和性能分析。
图画的比较挫,可以凑合着看,大概的流程酱紫。当然程序的执行路径和数据读写信息的被专有硬件的实时记录的过程是简化的,这些都是很多部分组成的,分散排放在SoC中。
简言之CoreSight可以帮助用户实现四大功能:SoC的调建,软件调试,系统配置,事后剖析调试。
除了ARM,当然别家也会推出这种Trace技术产品,不过对于我们的读者而言,CoreSight对其的意义应该是最大的吧。(小众的以后可以再扒)
WHAT:是啥
CoreSight你可以将其称之为一种技术,一种硬件,或者叫做一种系统级IP(这个应该是最准确的)。它是ARM公司于2004年推出的一种新的调试体系结构。
下图中是从ARM官网上down下来的一张关于CoreSight的说明图,图中的ETM,PMU,ETM,CTM,TPIU,STM,TMC以及Timestamp等都属于CoreSight的一部分;各个部分的功能也在上面标明了。
这么多可能看起来有点晕,总结来说CoreSight的调试结构中按照功能可以分为三大部分:Source,Link和Sink。
Source意即源部件,指一些信号或者profile/debug数据的来源,用于产生向ATB(AMBA Trace Bus)发送的跟踪数据,一般是APB总线。
比如STM和ETM都属于Source部分。
STM (System Trace Macrocell):用于获取系统的跟踪信息。
ETM (Embedded Trace Macrocell):用于获取处理器核的跟踪信息。
Sink意即控制访问部件,配置和控制数据流的产生,但是不产生数据流。 指一些可以保持这些从source过来数据的模块。
比如DAP和ECT都属于Sink部分。
DAP(Debug Access Port):可以实时访问AMBA总线上的系统内存,外设寄存器,以及所有调试配置寄存器,而不需挂起系统。
ECT(Embedded Cross Trigger):包括CTI(Cross Trigger Interface)和CTM(Cross Trigger Matrix),为ETM(Embedded Trace Macrocell)提供接口,用于将一个处理器的调试事件传递给另一个处理器。
Link意即汇聚点,芯片上跟踪数据的终点。指用于引导从source到sink过程中的类似于通道作用的模块。
比如TPIU、ETB和SWO都属于汇聚点。
TPIU(Trace Port Interface Unit):将片内各种跟踪数据源获取的信息按照TPIU帧的格式进行组装,然后通过Trace Port传送到片外。
ETB(Embedded Trace Buffer):一个32位的RAM,作为片内跟踪信息缓冲区。
SWO(Serial Wire Output):类似TPIU,但仅输出ITM单元的跟踪信息,只需要一个引脚。
How:DS5
上图是一个典型的CoreSight调试结构,上文中已经提及,在此不在赘述。这里主要说明一下如何在软件上实现How。开发者可以使用ARM DS Development Tools来实现CoreSight的调试和追踪,亦可选择超过25个第三方的开发工具。
其中DS5是目前最新的版本,其中主要包含三个组件,分别是编程工具,调试工具和流水线分析工具。DS-5 调试器兼具集成的微控制器工具的方便性和高生产率以及针对 Linux 的开放源工具的强大功能和灵活性。其基于 Eclipse 的 GUI 简化了管理不同目标连接类型的复杂工作,为在启动加载程序、内核和应用程序级别进行软件调试提供多种不同的方式,同时,其与 GDB 类似的命令行接口为专业的 Linux 用户增添了快速控制和脚本撰写功能。
篇幅受限,下面仅通过跟踪角度来简单讲述DS5的如何使用CoreSight的Trace功能,相信大家可以做到管窥一斑而知全豹。
非侵入性跟踪
在调试软件时,在许多情况下,错误的负面影响显而易见,但导致出现此类错误的根本原因却远在程序执行之前就已存在。DS-5 调试器所支持的 ARM CoreSight ETM 和 PTM 提供非侵入性程序跟踪,允许开发人员在出现错误时查看说明(以及关联的源代码)。它还使开发人员能够调试对时间敏感的问题,而用传统的侵入性单步技术将很难发现这些问题。DS-5 调试器目前使用 DSTREAM 来捕获 ETB 上的跟踪。还添加了对在此调试和跟踪探针中安装的 4 GB 片外跟踪缓冲区的支持。
灵活的跟踪显示 p<> 跟踪是用于调试和短期性能分析的宝贵工具。但是,只有非常少的开发人员能够应付已执行的机器指令的长长的列表。在 DS-5 中,十分重视以开发人员可以轻松地进行有意义的处理的高级语言来展示这些数据,例如将指令链接到相应的源代码、显示功能级别跟踪分析或者提供图形跟踪显示。
基于跟踪的分析
基于跟踪数据,DS-5 调试器还生成时间表图表,所含信息可帮助开发人员迅速理解其软件在目标上的执行方式以及哪些功能最占用 CPU。通过提供不同的缩放级别,该时间表可基于每个时间单位的指令数显示以其最高分辨率显示热点地图,并且提供按每组指令的典型延迟时间以不同颜色编码的每指令显示。
2 DBG介绍
GDB调试的三种方式:
1. 目标板直接使用GDB进行调试。
2. 目标板使用gdbserver,主机使用xxx-linux-gdb作为客户端。
3. 目标板使用ulimit -c unlimited,生成core文件;然后主机使用xxx-linux-gdb ./test ./core。
Brendan Gregg关于gdb介绍《gdb Debugging Full Example (Tutorial): ncurses》。
1. gdb调试
构造测试程序如下和如下:
然后gcc -o main -g, 得到main可执行文件.
下面介绍了gdb大部分功能, 设置断点以及 显示栈帧是常用功能;调试过程中可以需要 单步执行,并且 显示变量、显示寄存器、 监视点、 改变变量的值。
如果进程已经运行中,需要1 attach到进程,或者0 生成转储文件进行分析。当然为了提高效率可以自定义3 初始化文件。
设置断点
设置断点可以通过b或者break设置断点,断点的设置可以通过函数名、行号、文件名+函数名、文件名+行号以及偏移量、地址等进行设置。
格式为:
break 函数名
break 行号
break 文件名:函数名
break 文件名:行号
break +偏移量
break -偏移量
break *地址
查看断点,通过info break查看断点列表。
删除断点通过命令包括:
delete <断点id>:删除指定断点
delete:删除所有断点
clear
clear 函数名
clear 行号
clear 文件名:行号
clear 文件名:函数名
断点还可以条件断住
break 断点 if 条件;比如break sum if value==9,当输入的value为9的时候才会断住。
condition 断点编号:给指定断点删除触发条件
condition 断点编号 条件:给指定断点添加触发条件
如下可以看出,当入参为9的时候被断住,而入参为8的时候运行到结束。
断点还可以通过disable/enable临时停用启用。
disable
disable 断点编号
disable display 显示编号
disable mem 内存区域
enable
enable 断点编号
enable once 断点编号:该断点只启用一次,程序运行到该断点并暂停后,该断点即被禁用。
enable delete 断点编号
enable display 显示编号
enable mem 内存区域
.1 断点commands高级功能
大多数时候需要在断点处执行一系列动作,gdb提供了在断点处执行命令的高级功能commands。
比如需要对如上程序square参数i为5的时候断点,并在此时打印栈、局部变量以及total的值
编写如下:
在gdb shell中source ,然后r执行命令,结果如下:
可以看出断点在i==5的时候断住了,并且此时打印了正确的值。
运行
“gdb 命令”之后,run可以在gdb下运行命令;如果命令需要参数则跟在run之后。
如果需要断点在main()处,直接执行start就可以。
显示栈帧
如果遇到断点而暂停执行,或者coredump可以显示栈帧。
通过bt可以显示栈帧,bt full可以显示局部变量。
命令格式如下:
bt
bt full:不仅显示backtrace,还显示局部变量
bt N:显示开头N个栈帧
bt full N
显示变量
“print 变量”可以显示变量内容。
如果需要一行监控多个变量,可以通过p {var1, var2, var3}。
如果要跟踪自动显示,可以使用display {var1, var2, var3}
显示寄存器
info reg可以显示寄存器内容。
在寄存器名之前加$可以显示寄存器内容,
p $寄存器:显示寄存器内容
p/x $寄存器:十六进制显示寄存器内容。
用x命令可以显示内容内容,“x/格式 地址”。
x $pc:显示程序指针内容
x/i $pc:显示程序指针汇编。
x/10i $pc:显示程序指针之后10条指令。
x/128wx 0xfc207000:从0xfc20700开始以16进制打印128个word。
还可以通过disassemble指令来反汇编。
disassemble
disassemble 程序计数器 :反汇编pc所在函数的整个函数。
disassemble addr-0x40,addr+0x40:反汇编addr前后0x40大小。
单步执行
单步执行有两个命令next和step,两者的区别是next遇到函数不会进入函数内部,step会执行到函数内部。
如果需要逐条汇编指令执行,可以分别使用nexti和stepi。
继续执行
调试时,使用continue命令继续执行程序。程序遇到断电后再次暂停执行;如果没有断点,就会一直执行到结束。
continue:继续执行
continue 次数:继续执行一定次数。
监视点
要想找到变量在何处被改变,可以使用watch命令设置监视点watchpoint。
watch <表达式>:表达式发生变化时暂停运行
awatch <表达式>:表达式被访问、改变是暂停执行
rwatch <表达式>:表达式被访问时暂停执行
其他变种还包括watch expr [thread thread-id] [mask maskvalue],其中mask需要架构支持。
GDB不能监控一个常量,比如watch 0x600850报错。
但是可以watch *(int *)0x600850。
改变变量的值
“通过set variable <变量>=<表达式>”来修改变量的值。
set $r0=xxx:设置r0寄存器的值为xxx。
0 生成内核转储文件
通过“generate-core-file”生成转储文件。
然后gdb ./main ./查看恢复的现场。
另一命令gcore可以从命令行直接生成内核转储文件。
gcore `pidof 命令`:无需停止正在执行的程序已获得转储文件。
1 attach到进程
如果程序已经运行,或者是调试陷入死循环而无法返回控制台进程,可以使用attach命令。
attach pid
通过ps aux可以查看进程的pid,然后使用bt查看栈帧。
以top为例操作步骤为:
1. ps -aux查看进程pid,为16974.
2. sudo gdb attach 16974,使用gdb 附着到top命令。
3. 使用bt full查看,当前栈帧。此时使用print等查看信息。
4. 还可以通过info proc查看进程信息。
2 反复执行
continue、step、stepi、next、nexti都可以指定重复执行的次数。
ignore 断点编号 次数:可以忽略指定次数断点。
3 初始化文件
Linux环境下初始化文件为.gdbinit。
如果存在.gdbinit文件,gdb在启动的之前就将其作为命令文件运行。
初始化文件和命令文件执行顺序为:HOME/.gdbinit > 运行命令行选项 > ./.gdbinit > -x指定命令文件。
4 设置源码目录
调试过程中如果需要关联到源码,查看更详细的信息。
可以通过directory或者set substitute-path来制定源码目录。
5 TUI调试
TUI(TextUser Interface)为GDB调试的文本用户界面,可以方便地显示源代码、汇编和寄存器文本窗口。
源代码窗口和汇编窗口会高亮显示程序运行位置并以'>'符号标记。有两个特殊标记用于标识断点,第一个标记用于标识断点类型:
B:
程序至少有一次运行到了该断点
b:
程序没有运行到过该断点
H:
程序至少有一次运行到了该硬件断点
h:
程序没有运行到过该硬件断点
第二个标记用于标识断点使能与否:
+:
断点使能Breakpointis enabled. -:
断点被禁用Breakpointis disabled.
当调试程序时,源代码窗口、汇编窗口和寄存器窗口的内容会自动更新。
6 Catchpoint
catch可以根据某些类型事件来停止程序执行。
可以通过catch syscall close,捕捉产生系统调用close的时候停止程序执行。
其他的catch事件还包括,throw、syscall、assert、exception等等。
7 自定义脚本
命令行的入参可以通过argc和*argv获取。
7.0 注释、赋值、显示
# - 为脚本添加注释。
set - 为变量赋值,以$开头,以便区分gdb还是调试程序变量。
例如:set $x = 1
显示变量可以通过echo、printf。
7.1自定义命令
利用define命令可以自行定义命令,还可以使用document命令给自定义命令添加说明。
执行bf自定义命令,结果如下。
无行参声明,但可以直接用$arg0,$arg1引用, $argc 为形参个数
7.2 条件语句
条件命令:if...else...end
。这个同其它语言中提供的if
命令没什么区别,只是注意结尾的end
。
7.3 循环语句
循环命令:while...end
。gdb同样提供了loop_break
和loop_continue
命令分别对应其它语言中的break
和continue
,另外同样注意结尾的end
。
在命令行gdb -x bin;或者gdb bin,然后在命令行soruce 同样可以更新脚本。
8 dump内存到指定文件
在gdb调试中可能需要将一段内存导出到文件中,可以借助dump命令。
命令格式:
dump binary memory FILE START STOP
比如dump binary memory ./ 0x0 0x008000000,将内存区间从0x0到0x00800000导出到中。
2. gdb+gdbserver远程调试
目标板gdbserver+主机gdb远程调试的方式,比较适合目标板性能受限,只能提供gdbserver功能。
在主机上执行gdb进行远程调试。测试程序如下。
对目标板的设置方式是:开启端口2345作为gdbserver铜线端口。
gdbserver :2345 test_debug
主机上执行gdb test_debug,然后tar remote 连接远程gdbserver。
目标板会收到“Remote debugging from host 192.168.33.77”消息,表示两者连接成功。
主机上就可以进行远程调试,continue之后两端得到的结果如下:
目标板输出“a=0x12”之后停止运行,。
主机上得到SIGSEGV,并可以查看backtrace信息。可以看出问题点在指针p指向NULL,0指针赋值错误。
3. 通过core+gdb离线分析
在目标板上执行ulimit -c unlimited,执行应用程序。
程序出错后,会在当前目录下生成core文件。
将core文件拷出后,再PC上执行xxx-linux-gdb ./test ./core进行分析。
加载库文件
在运行xxx-linux-gdb ./test ./core之后,可能存在库文件关联不上的情况。
使用info sharedlibrary,查看库加载情况。
可以通过set solib-search-path和set solib-absolute-prefix来设置,对应库所在的路径。
可以看出相关库文件都已经加载,只是部分库文件没有调试信息。
查看backtrace
查看coredump的backtrace通过bt即可,更全的信息通过bt full。
产看函数调用栈的几个函数
bt:显示所有的函数调用栈帧的信息,每个帧一行。
bt n:显示栈定的n个帧信息。
bt -n:显示栈底的n个帧信息。
bt full:显示栈中所有帧的完全信息如:函数参数,本地变量。
bt full n:用法同上。
bt full -n
(gdb) bt
#0 0x2ad71f1e in memcpy () from xxx/lib/libc.so.6
#1 0x2ad71ac0 in memmove () from xxx/lib/libc.so.6
#2 0x0011f36c in std::__copy_move::__copy_m (__first=0x34dfb008 "\377\330\377", , __last=0x34eeea2c "",
...
#3 0x0011ee22 in std::__copy_move_a (__first=0x34dfb008 "\377\330\377", , __last=0x34eeea2c "", __result=0x2b2013c0 "\377\330\377", )
at xxxinclude/c++/6.3.0/bits/stl_algobase.h:386
#4 0x0011e7e2 in std::__copy_move_a2 >, unsigned char*> (__first=..., __last=..., __result=0x2b2013c0 "\377\330\377", )
at xxx/bits/stl_algobase.h:424
#5 0x0011dfd2 in std::copy<__gnu_cxx::__normal_iterator >, unsigned char*> (__first=..., __last=..., __result=0x2b2013c0 "\377\330\377", )
at xxx/6.3.0/bits/stl_algobase.h:456
#6 0x0011c948 in xxx
#7 0x00133e08 in xxx
#8 0x2aada31e in start_thread () from xxx/libc/lib/libpthread.so.0
#9 0x005a11b4 in ?? ()
3.3 Core Dump核心转存储文件目录和命名规则
默认情况下core文件存在应用当前路径下,为了区分可以进行设置。
区分core主要通过/proc/sys/kernel/core_uses_pid和/proc/sys/kernel/core_pattern进行设置。
/proc/sys/kernel/core_uses_pid:可以控制产生的core文件的文件名中是否添加pid作为扩展,如果添加则文件内容为1,否则为0。
proc/sys/kernel/core_pattern:可以设置格式化的core文件保存位置或文件名,比如原来文件内容是core-%e
echo "/tmp/core-%e-%p" > core_pattern。
将会控制所产生的core文件会存放到/corefile目录下,产生的文件名为core-命令名-pid-时间戳
以下是参数列表:
%p - insert pid into filename 添加pid
%u - insert current uid into filename 添加当前uid
%g - insert current gid into filename 添加当前gid
%s - insert signal that caused the coredump into the filename 添加导致产生core的信号
%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间
%h - insert hostname where the coredump happened into filename 添加主机名
%e - insert coredumping executable name into filename 添加命令名
当然,你可以用下列方式来完成:
sysctl -w kernel.core_pattern=/tmp/core-%e-%p
3.4 ulimit的使用
功能说明:控制shell程序的资源。
语 法:ulimit [-aHS][-c
补充说明:ulimit为shell内建指令,可用来控制shell执行程序的资源。
参 数:
-a 显示目前资源限制的设定。
-c
-d <数据节区大小> 程序数据节区的最大值,单位为KB。
-f <文件大小> shell所能建立的最大文件,单位为区块。
-H 设定资源的硬性限制,也就是管理员所设下的限制。
-m <内存大小> 指定可使用内存的上限,单位为KB。
-n <文件数目> 指定同一时间最多可开启的文件数。
-p <缓冲区大小> 指定管道缓冲区的大小,单位512字节。
-s <堆叠大小> 指定堆叠的上限,单位为KB。
-S 设定资源的弹性限制。
-t
-u <程序数目> 用户最多可开启的程序数目。
-v <虚拟内存大小> 指定可使用的虚拟内存上限,单位为KB。
4. GDB小技巧
关闭 Type to continue, or q to quit---
当现实内容多的时候,GDB会强制分页,现实就会暂停。但是可能并不需要,可以通过set pagination off关闭。
附着到已运行kernel
在已运行的Linux上,如果发生死机异常等问题,这时候定位问题需要使用jtag连接上。
连接方法是:
gdb-----------------------------------------------进入gdb shell。
target remote localhost:1025-------------------在gdb shell中通过ip:port连接上target。
file vmlinux----------------------------------------加载符号表。
然后就可以在线查看运行状态了。