文章目录
- OpenRemoved_Tina_Linux_系统调试_使用指南
- 1 概述
- 1.1 编写目的
- 1.2 适用范围
- 1.3 相关人员
- 2 调试方法及工具
- 2.1 内核日志
- 2.2 GDB
- 2.2.1 介绍
- 2.2.2 配置
- 2.2.3 使用
- 2.2.4 更多用法
- 2.2.5 注意事项
- 2.3 gdbserver.
- 2.3.1 介绍
- 2.3.2 配置
- 2.3.3 使用
- 2.4 coredump.
- 2.4.1 介绍
- 2.4.2 配置
- 2.4.3 配置生成coredump文件.
- 2.4.4 通过gdb定位问题
- 2.5 perf
- 2.5.1 介绍
- 2.5.2 配置
- 2.5.3 使用
- 2.6 strace
- 2.6.1 介绍
- 2.6.2 配置
- 2.6.3 使用
- 2.7 valgrind.
- 2.7.1 介绍
- 2.7.2 配置
- 2.7.3 使用
- 2.8 轻量级日志永久转存.
- 2.8.1 使能日志转存.
- 2.8.1.1 使能内核功能模块
- 2.8.1.2 指定分区
- 2.8.2 获取奔溃日志.
- 2.8.2.1 挂载文件系统.
- 2.8.2.2 读取文件
- 2.8.2.3 删除文件
- 2.8.3 高级功能配置.
- 2.8.3.1 分区的空间分布
- 2.8.3.2 高级功能
OpenRemoved_Tina_Linux_系统调试_使用指南
1 概述
1.1 编写目的
本文主要服务于使用Tina软件平台的广大客户,帮助开发人员方便快速了解Tina平台系统调试工具。
1.2 适用范围
本文适用于Tina3.5版本以上软件平台;对硬件环境没有要求,所有Allwinner硬件平台都适 用。
其中,注意linux-5.4内核上暂未支持pstore功能。
1.3 相关人员
适用Tina平台的广大客户与开发人员。
2 调试方法及工具
2.1 内核日志
内核日志默认打印在env.cfg中配置,文件路径:
代码语言:javascript复制文件一般在芯片方案配置目录下,例如:
device/config/chips/v853/configs/perf1/linux/env-4.9.cfg
device/config/chips/r528/configs/evb2/env.cfg
文件中的loglevel决定打印等级
loglevel=
在进入系统后,有下面方法可以修改打印等级:
代码语言:javascript复制echo 8 > /proc/sys/kernel/printk
dmesg -n 8
2.2 GDB
2.2.1 介绍
GDB(GNU symbolic debugger)是GNU开源组织发布的一款调试工具,用于调试由GCC编译的代码。它的功能非常强大,使用命令行的调试方式,允许调试复杂的应用程序,给程序开发提供了极大的便利。
2.2.2 配置
Tina SDK中GDB源码包位于dl目录下,默认不配置GDB软件包,使用时需要先选上GDB。配置方法如下。
代码语言:javascript复制make menuconfig -->
Development -->
<*> gdb------------------------------------------------------- GNU Debugger
2.2.3 使用
按照上述方法配置好GDB后,重新编译并烧写系统,在设备端口运行gdb即可调试应用程序。
代码语言:javascript复制gdb <process_name>
2.2.4 更多用法
gdb调试命令很多,如何使用可以参考:https://www.gnu.org/software/gdb/documentation/
2.2.5 注意事项
- 调试信息 gdb主要用来调试C/C 的程序。在编译源码时必须要把调试信息加到可执行文件中。即编译参数带上-g参数。如果没有-g,将看不见程序的函数名和变量名,代替它们的全是运行时的内存地址。
- 多线程调试 参考:https://sourceware.org/gdb/onlinedocs/gdb/Forks.html
- 已运行进程调试 gdb attach -p ,其中pid为需要调试的进程名字。
2.3 gdbserver.
2.3.1 介绍
gdbserver是可以对目标设备上的程序进行远程调试的软件。
2.3.2 配置
代码语言:javascript复制make menuconfig -->
Development -->
<*> gdbserver................................. Remote server for GNU Debugger
2.3.3 使用
- 先确定本地回环接口是否打开,如未打开需要先进行网络配置,在小机端执行以下命令。
ip addr add dev lo 127.0.0.1/32 //设置本地回环地址为127.0.0.
ifconfig lo up //使能端口
- 在小机端运行gdbserver程序
gdbserver 127.0.0.1:3456 process //3456为目标板端口号,用户自己定义,process为应用程序名字
- 在主机端做adb端口映射
adb forward tcp:3456 tcp:3456 //第一个 3456 为主机端口,第二个 3456 为目标板端口
- 在主机使用gdb
${PC端编译工具链路径}/arm-openwrt-linux-gnueabi-gdb process
- 主机端进行进入gdb界面,执行
target remote :
- 连接正确可开始调试程序,最开始会从_start函数开始,所以可以先执行下边调试指令,进入 应用程序的main函数进行调试。
b main
c
2.4 coredump.
2.4.1 介绍
程序运行过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做CoreDump。
可以认为CoreDump是内存快照,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时记录下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。
CoreDump对于调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而CoreDump文件可以再现程序出错时的情景。
技巧:man core 可以看到 core dump file 详细说明。 man 7 signal 可以看到信号详细说明。
2.4.2 配置
代码语言:javascript复制tina根目录下,make kernel_menuconfig,选中以下配置。
Userspace binary formats -->
[*] Enable core dump support
涉及到的内核配置:CONFIG_COREDUMP
2.4.3 配置生成coredump文件.
代码语言:javascript复制(1) ulimit -c unlimited;
(2) echo ‘core.%e.%p’> /proc/sys/kernel/core_pattern;
(1)表示在异常时产生core dump文件,不对core dump文件的大小进行限制。
(2)指定core dump文件的存储位置及名称,表示产生的core文件中将带有崩溃的程序名、以 及它的进程ID
代码语言:javascript复制core_pattern的格式说明:
%%单个%字符
%p所dump进程的进程ID
%u所dump进程的实际用户ID
%g所dump进程的实际组ID
%s导致本次core dump的信号
%t core dump的时间(由 1970 年 1 月 1 日计起的秒数)
%h主机名
%e程序文件名
具体可以通过man core查看
技巧: core dump 文件默认存放在 tmp 目录下,如果有指定目录 , 注意目录必须存在, coredump 不支持创建目录。 /proc/sys/ker-nel/core_uses_pid ,内容为 1 ,一定会加上进程 ID ,即使 core_pattern 中没有 %p 。
2.4.4 通过gdb定位问题
生成coredump文件后(例如/tmp/core),gdb运行该文件:
代码语言:javascript复制./gdb coredump_sample /tmp/core
具体可以查看gdb或者gdbserver章节描述。
2.5 perf
2.5.1 介绍
Perf是从Linux 2.6开始引入的一个profiling工具,通过访问包括pmu在内的软硬件性能计数器来分析性能,支持多架构,是目前Kernel的主要性能检测手段,和Kernel代码一起发布,所以兼容性良好。
性能瓶颈如果要分类的话,大致可以分为几个大类: cpu/gpu/mem/storage ,其中gpu用Perf没法直接探测(这个目前比较好用的工具就只有DS5),storage一般用tracepoint来统计。总的说来,Perf还是侧重于分析cpu的性能,其他功能都不是很好用。常用的功能有以下几个。
- record:收集profile数据
- report:根据profile数据生成统计报告
- stat:打印性能计数统计值
- top:cpu占有率实时统计
2.5.2 配置
perf工具依赖内核选上PERF_EVENTS等配置,具体配置介绍如下:
代码语言:javascript复制支持perf基本功能选项,必须打开:
CONFIG_PERF_EVENTS=y
CONFIG_HW_PERF_EVENTS=y
支持堆栈跟踪:
CONFIG_FRAME_POINTER=y
支持解析内核和外部包符号,GCC编译时加上-g选项:
CONFIG_KALLSYMS=y
支持TRACEPOINTS:
CONFIG_FTRACE=y
支持内核态动态跟踪:
CONFIG_KPROBES=y
CONFIG_KPROBE_EVENT=y
支持用户态动态tracepoint跟踪:
CONFIG_DEBUG_INFO=y
//以下配置需要执行make kernel_menuconfig进行配置
支持用户态动态跟踪:
CONFIG_UPROBES=y
CONFIG_UPROBE_EVENTS=y
支持内核态lock跟踪:
CONFIG_LOCKDEP=y
kernel lock tracing:
CONFIG_LOCK_STAT=y
支持TRACEPOINTS:
CONFIG_TRACEPOINTS=y
注意:部分内核不支持用户态动态跟踪,例如linux3.4,具体是否支持,内核搜索是否有该配置 选项即可。
上述介绍的配置都是内核的配置,Tina中直接通过make menuconfig可以选上部分配置,配 置方式如下:
代码语言:javascript复制tina根目录下,make menuconfig,选中以下配置:
Global build settings --->
[*] Compile the kernel with frame pointers
[*] Compile the kernel with symbol table information
-*- Compile the kernel with tracing support
[*] Compile the kernel with kprobes support
Development --->
<*> perf............. Linux performance monitoring tool
选上上述配置之后编译即可,部分使用者需要修改 perf编译工具的编译参数,可配置pack- age/devel/perf/Makefile中MAKE_FLAGS参数,修改其中的NO_XXX=1。修改之后会新 增依赖,相应的先编译依赖再编译perf。
2.5.3 使用
代码语言:javascript复制root@TinaLinux:/# perf stat /bin/perftest
Starting convolution! thread = 4 ,count = 2
Finished convolution! Time consumed 20 seconds.
Performance counter stats for '/bin/perftest':
20236.937258 task-clock # 0.994 CPUs utilized
2404 context-switches # 0.119 K/sec
0 CPU-migrations # 0.000 K/sec
1572 page-faults # 0.078 K/sec
24241775385 cycles # 1.198 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
7514299585 instructions # 0.31 insns per cycle
621110448 branches # 30.692 M/sec
1134868 branch-misses # 0.18% of all branches
20.352726051 seconds time elapsed
2.6 strace
2.6.1 介绍
Strace通过ptrace系统调用来跟踪进程调用syscall的情况。
2.6.2 配置
代码语言:javascript复制tina根目录下, 运行make menuconfig,选择
Utilities --->
<*> strace............................ System call tracer
2.6.3 使用
- strace启动程序的同时用strace跟踪。
- strace -p pid对于已经启动的程序通过-p参数attach上去。
2.7 valgrind.
2.7.1 介绍
Valgrind是一套Linux下,开放源代码(GPLv2)的仿真调试工具的集合。由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件(plug-in),利用内核提供的服务完成各种特定的内存调试任务。Valgrind包括以下工具,Tina平台使用较多的工具是memcheck,用来检查应用程序内存泄漏情况。
- Memcheck:内存使用情况检查。
- Callgrind:收集程序运行时的一些数据,函数调用关系等信息。
- Cachegrind:模拟CPU中的一级缓存I1,D1和L2二级缓存,能够精确地指出程序中cache的丢失和命中。
- Helgrind:用来检查多线程程序中出现的竞争问题。
- Massif:堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈 的大小。
2.7.2 配置
代码语言:javascript复制tina根目录下, 运行make menuconfig,选择
Development -->
<*> valgrind .........................debugging and profiling tools for linux
2.7.3 使用
代码语言:javascript复制valgrind --tool=memcheck --leak-check=full {program}
2.8 轻量级日志永久转存.
全志轻量级日志永久转存方案依赖于内核原生的pstore文件系统,设计了pstore/blk模块,配 合全志的Flash驱动,实现在内核奔溃时,自动把日志转存到Flash中,并在开机后以文件形式 呈现到用户空间。
此方案在全志释放的Linux-4.9及之后的内核版本中支持,暂时不兼容Linux-3.4/3.10/4.4等 旧内核版本。
pstore/blk 模块及其衍生的 pstore/zone , mtdpstore 模块已合并进Linux社区。详细的 使用文档可参考社区内核文档。
代码语言:javascript复制Documentation/admin-guide/pstore-blk.rst
全志的实现支持社区的所有Frontend功能,包括:
- kmsg -内核Panic/Oops/emerg/restart/halt/poweroff时的日志信息。
- pmsg -用户空间的信息转存(Android用于存储系统日志)。
- ftrace - ftrace信息。
- console -串口终端信息。
在pstore中,kmsg前端基于kmsg_dump的机制,在最新的版本中支持所有的kmsg_dump_reason。kmsg_dump机制可以在特定时机出发回调,把内核的日志缓存log_buf导出。
在pstore中,pmsg是pstore提供的用户空间转存信息的方法。用户空间程序把需要记录的信息写入到/dev/pmsg0的设备节点,在重启时,即可在pstore的挂载目录中获取写入的信息。在Android平台把pmsg用于存储系统日志。
当前不同存储介质对Frontend的支持情况如下表。
表2-1: pstore支持的Frontend
介质 | panic | oops | pmsg | ftrace | console |
---|---|---|---|---|---|
nor | N | Y | N | N | N |
(ubi) | spinand | N | Y | N | N |
(nftl) | spinand | Y | Y | Y | Y |
mmc | Y | Y | Y | Y | Y |
rawnand | Y | Y | Y | Y | Y |
! 警告:并不是所有的 rawnand/(nftl) spinand 都支持所有的 Fronend 功能,以实际驱动为准。
2.8.1 使能日志转存.
日志永久转存的方案,除了内核使能pstore/blk之外,还需要为其提供一个专用分区。因此使能日志转存有两个步骤。
- 使能内核功能模块
- 指定分区
2.8.1.1 使能内核功能模块
进入内核的 menuconfig ,在Tina平台可以在任意目录执行: m kernel_menuconfig
代码语言:javascript复制[kernel menuconfig]
|-> File systems
|-> Miscellaneous filesystems
|-> [*] Persistent store support
|-> Log panic/oops to a block device
|-> block device identifier
|-> Size in Kbytes of kmsg dump log to store
|-> Maximum kmsg dump reason to store
|-> Size in Kbytes of pmsg to store
|-> Size in Kbytes of console to store
上述的属性配置,例如 block device identifier 可以通过 h 按键获取详细的说明。这些属性 配置同时支持Kconfig和Module Parameters的两种配置方式,且 Module Parameters 具 有更高的优先级。
- block device identifier 指定使用的块设备
- Size in Kbytes of kmsg dump log to store 为kmsg前端分配的空间大小
- Maximum kmsg dump reason to store kmsg dumper支持的reason最大值(见enum kmsg_dump_reason)
- Size in Kbytes of pmsg to store 为pmsg前端分配的空间大小
- Size in Kbytes of console to store 为console前端分配的空间大小
技巧:block device identifier 见指定分区章节,其他属性使用默认配置即可。
2.8.1.2 指定分区
为内核pstore/blk模块指定使用的块设备分区,首先我们创建一个小容量分区,容量大小建议 [256K-1M] ,参考下表。
表2-2: pstore分区大小建议
Flash容量 | 建议大小 |
---|---|
容量<= 128M | 256K |
128M <容量<= 1G | 512K |
容量> 1G | 1M |
在sys_partition.fex中添加pstore分区,例如:
代码语言:javascript复制[partition]
name = pstore
size = 512
user_type = 0x
在创建了分区后,需要“告知”内核模块使用哪个分区。如上文所述,目前为止pstore/blk支 持Kconfig和Module Parameters两种配置方式。Kconfig比较简单,因此下文主要是讲解 Module Parameters的配置方式。
Module Parameter要不在手动加载模块时指定:
代码语言:javascript复制# insmod pstore_blk.ko blkdev=XXXX
如果是编译进内核,需要在内核 cmdline 中添加内核模块参数。
在全志平台,需要修改 env-XXX.cfg 。在对应存储介质的 setargs_XXX 中添加如下内容。
代码语言:javascript复制pstore_blk.blkdev=<分区路径>
例如:
代码语言:javascript复制setargs_mmc=... pstore_blk.blkdev=/dev/mmcblk0p ...
除了路径之外,还可以使用如下的形式。
代码语言:javascript复制pstore_blk.blkdev=<主设备号:次设备号>
其中 主设备号 表示的存储介质, 次设备号 代指哪个分区。
我们可以在进入到命令行后,通过ll命令获取主次设备号,例如:
代码语言:javascript复制$ ll /dev/mmcblk0*
brw------- 1 root root 179, 0 Jan 2 04:20 /dev/mmcblk
brw------- 1 root root 179, 16 Jan 2 04:20 /dev/mmcblk0boot
brw------- 1 root root 179, 32 Jan 2 04:20 /dev/mmcblk0boot
brw------- 1 root root 179, 1 Jan 2 04:20 /dev/mmcblk0p
brw------- 1 root root 179, 2 Jan 2 04:20 /dev/mmcblk0p
brw------- 1 root root 179, 3 Jan 2 04:20 /dev/mmcblk0p
brw------- 1 root root 179, 5 Jan 2 04:20 /dev/mmcblk0p
以 /dev/mmcblk0p5 为例, 主设备号是 179 ,从设备号是 5 ,因此cmdline可以写为blkoops.blkdev=179:
下面进一步说明 pstore分区 的对应关系:
在Tina个别平台做了进一步封装,只需要在 env-XXXX.cfg 中添加blkoops_partition=<分区名>和blkoops_blkdev=<分区路径|设备号>,例如:
代码语言:javascript复制blkoops_partition=pstore #分区名对应sys_partition.fex
blkoops_blkdev=93:7 #可先任意写一个
setargs_nand=... pstore_blk.blkdev=${blkoops_blkdev} ...
uboot则会根据blkoops_partition的分区名,自动匹配和修改blkoops_blkdev。
对于不支持进一步封装的方案,可在启动后查询cmdline的partitions参数,例如:
代码语言:javascript复制$ cat /proc/cmdline
.... partitions=boot-res@mmcblk0p2:env@mmcblk0p5:boot@mmcblk0p6....
OK,到此日志永久转存的功能已经使能。
2.8.2 获取奔溃日志.
2.8.2.1 挂载文件系统.
全志轻量级日志转存的方案基于的是pstore文件系统,因此需要挂载文件系统后才能使用。
在Tina平台中,pstore文件系统已经实现默认开机自动挂载,可以通过 mount 命令确认,例如:
代码语言:javascript复制# mount
···
pstore on /sys/fs/pstore type pstore (rw,relatime)
···
Android平台,需要自行实现挂载,挂载命令可参考:
代码语言:javascript复制mount -t pstore pstore /sys/fs/pstore
挂载后,在触发日志转存重启后,可以在挂载点 /sys/fs/pstore 中可获取奔溃日志文件,例如:
代码语言:javascript复制root@TinaLinux:/sys/fs/pstore# ll
drwxr-x--- 2 root root 0 Jan 1 1970.
drwxr-xr-x 5 root root 0 Jan 1 1970 ..
-r--r--r-- 1 root root 15504 Mar 19 19:39 dmesg-pstore-blk-
-r--r--r-- 1 root root 15881 Mar 19 19:39 dmesg-pstore-blk-
-r--r--r-- 1 root root 2 Jan 1 1970 pmsg-pstore-blk-
root@TinaLinux:/sys/fs/pstore#
可以通过命令 echo c > /proc/sysrq-trigger 主动触发内核奔溃以验证功能。
2.8.2.2 读取文件
奔溃日志会以文件形式呈现到挂载点,一次奔溃一份日志,文件名格式如下。
代码语言:javascript复制<日志类型>-pstore-blk-<编号>
我们可通过标准的IO接口访问导出的日志文件。
我们可以通过名字区分dmesg日志记录和psmg日志记录,但dmseg日志记录如何细分pan-ic/oops/oom呢?
在dmesg日志记录的第一行可以进一步细分日志类型和触发次数累计,例如:
代码语言:javascript复制root@TinaLinux:/sys/fs/pstore# head -n 3 dmesg-pstore-blk-
OOM: Total 8 times
OOM#8 Part
<4>[ 95.111229] [<c0018e48>] (do_page_fault) from [<c0009344>] (do_PrefetchAbort 0x38/
x9c)
除此之外, 文件时间表示的是奔溃触发时间
2.8.2.3 删除文件
可以直接删除生成的日志文件
代码语言:javascript复制rm /sys/fs/pstore/*
对使用mtdpstore模块的spinor/(ubi) spinand存储方案,考虑到存储物料的擦除特性,当同 时存在多个连续文件,且刚好这些文件数据存储在同一个物理块内时,要把同一个块内的文件全 部删除后才会真正删除文件。
2.8.3 高级功能配置.
2.8.3.1 分区的空间分布
默认情况下,pstore/blk的每一份记录为64K。意味着如果分区大小为256K,则一共能同时存 在 4 份记录。假设只使能kmsg和pmsg的记录,此时分区的划分情况大致如下表:
表2-3: pstore分区分布
0 - 64K | 64k - 128K | 128K - 192K | 192K - 256 K |
---|---|---|---|
pmsg | dmesg.0 | dmesg.1 | dmesg. |
显而易见,在划分了pmsg的空间后,剩余的空间全部分配给dmesg。
2.8.3.2 高级功能
内核模块通过cmdline中传递模块参数,可设置高级功能。日志永久转存模块支持以下模块参 数。推荐使用默认配置
表2-4: pstore支持参数
模块名 | 功能 | 示例 | 默认值 |
---|---|---|---|
pstore_blk.blkdev | 供blkoops使用的分区 | pstore_blk.blkdev=179:10 | NULL |
pstore_blk.oops_size | dmesg记录大小 | pstore_blk.oops_size=64 | 64KB |
pstore_blk.pmsg_size | pmsg记录大小 | pstore_blk.pmsg_size=64 | 64KB |
pstore_blk.console_size | console记录大小 | pstore_blk.console_size=64 | 64KB |
pstore_blk.ftrace_size | ftrace记录大小 | pstore_blk.ftrace_size=64 | 64KB |
pstore_blk.dump_oops | 是否记录Oops日志 | pstore_blk.dump_oops=1 | True |
pstore.update_ms | 定时刷新日志信息 | pstore.update_ms=1000 | -1 |
技巧:默认情况下,只有重启后才会刷新 pstore 的记录,除非使能了 pstore.update_ms 。