电子说
在Linux系统中,若程序异常终止,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为叫做Core Dump(中文一般译为“核心转储”)。实际上,除内存信息之外,核心转储还会记录程序的一些关键运行状态,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息等。核心转储对于程序员调试程序非常有益,因为有些程序错误是很难重现的,例如指针异常,而核心转储文件可以重现程序出错时的情景。
1
Ubuntu 16.04系统中打开核心转储功能
Ubuntu 16.04系统默认关闭了核心转储功能,需重新设置打开。
1检查核心转储是否打开
按快捷键“Ctrl+Alt+T”打开命令终端,输入命令:
ulimit -c |
若输出的结果为 0,则说明默认是关闭核心转储功能的,即当程序异常终止时,不会生成核心转储文件。
2在当前命令终端中打开核心转储
使用命令:
ulimit -c unlimited |
可开启当前命令终端的核心转储功能,并且不限制核心转储文件大小; 若需限制文件大小,将unlimited修改为你所需文件的大小,注意单位为KB。
3永久打开核心转储
若想永久打开核心转储功能,则使用命令:
sudo vi /etc/security/limits.conf |
修改配置文件(我这里是使用vi编辑器修改,你可以换成自己熟悉的编辑器,但建议修改配置文件还是采用vi较好,因为它是所有Unix/Linux系统标配的编辑器,并且简单的操作并不困难),增加如下所示的一行内容:
#Each line describes a limit for a user in the form:## |
4配置核心转储文件名是否添加PID号
默认的核心转储文件名称为core。通过修改/proc/sys/kernel/core_uses_pid文件可以让生成的core文件名是否自动加上pid号。使用命令:
sudo vi /proc/sys/kernel/core_uses_pid |
将/proc/sys/kernel/core_uses_pid文件里的“0”修改为“1”,然后保存退出,这样生成的core文件名将会变成core.pid,其中pid表示该进程的PID。
5配置核心转储文件的生成位置及文件名格式
默认的核心转储文件保存在可执行文件所在的目录下,可以通过修改/proc/sys/kernel/core_pattern文件来控制core文件的生成位置以及文件名格式。使用命令:
sudo vi /proc/sys/kernel/core_pattern |
可对core文件的生成位置以及文件名格式进行配置,以下是几种配置示例:
# 示例1:将生成的core文件保存在/apollo/data/core目录下,# 文件名格式:“core_进程名.进程PID”/apollo/data/core/core_%e.%p# 示例2:将生成的core文件保存在/tmp/core目录下,# 文件名格式:“core_进程名_进程PID.时间戳”/tmp/core/core_%e_%p.%t# 示例3:这是Ubuntu默认的core文件生成方式。# “apport”是一个用python写的脚本程序,# 其作用是在可执行文件目录下生成core文件,# %p %s %c %d %P分别表示: |
注意:以上示例只能使用其中一个,关于core文件的详细命名格式,可以通过man core命令查看。
2
Ubuntu 16.04系统中调试核心转储文件的一个示例
1生成核心转储文件
首先撰写一个C++测试程序,代码如下:
#include |
Linux系统中使用GCC编译器的编译命令如下:
g++ -g -Wall -std=c++11 *.cpp -o test |
注意,上述命令一定要加“-g”选项,生成调试信息,否则后面使用GDB调试核心转储文件时,仍然无法定位程序崩溃点。
运行该程序:
./test |
输出结果为:
段错误 (核心已转储) |
ls -l的结果如下(我使用示例3所示的core文件生成方式):
总用量 584-rw------- 1 davidhopper davidhopper 565248 Mar 19 17:49 core-rw-rw-r-- 1 davidhopper davidhopper 163 Mar 19 16:27 main.cpp-rwxrwxr-x 1 davidhopper davidhopper 25968 Mar 19 17:49 test |
可见,已经在当前可执行文件目录中生成了一个核心转储文件:core
2使用GDB调试器调试core文件
借助GDB调试器,使用如下命令,可调试core文件:
gdb ./test core |
输出信息如下:
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1Copyright (C) 2016 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later |
可见,GDB调试器根据生成的core文件,已经找到了程序崩溃点为原程序的第8行。看到这里,是不是感觉到了GDB调试核心转储文件的威力?
3
Ubuntu 16.04系统中调试Apollo项目核心转储文件的方法
由于Apollo项目是在Docker中运行,因此不能直接在Ubuntu 16.04系统中直接生成核心转储文件并使用GDB对其进行调试,所有的工作必须在Docker中完成。具体操作步骤如下:
1启动并进入Apollo项目的Docker
# 进入Apollo项目根目录(我的路径为:~/code/apollo,你需要修改为自己的路径)cd ~/code/apollo# 启动Apollo项目的Docker(注意:2.0以上版本在后面加上一个“-C”选项,# 表示从中国服务器拉取镜像文件,以加快下载速度)bash docker/scripts/dev_start.sh# 进入Dockerbash docker/scripts/dev_into.sh |
2在Docker内部检查并设置核心转储功能
在Docker内部,使用本文第一部分内容检查并设置核心转储功能。在我的机器上,使用ulimit -c命令检查的结果为:unlimited,表明已打开核心转储功能。假如在你的Docker内部发现未开启核心转储功能,该怎么办?那就按照本文第一部分内容重新打开呗。
同样在我的Docker内部,使用命令:cat /proc/sys/kernel/core_pattern查看核心转储文件的生成位置及文件名格式,得到的结果为:/apollo/data/core/core_%e.%p,表明Docker内部的核心转储文件被保存在/apollo/data/core目录下,文件名格式:core_进程名.进程PID。当然,你也可以按照本文第一部分内容对核心转储文件的保存位置及文件名格式进行定制。
3在Docker内部调试各功能模块生成的核心转储文件的方法
在Apollo项目Docker内部,所有功能模块的可执行文件均被放置于/apollo/bazel-bin/modules。下面以规划(Planning)模块为例进行说明。
最近,我修改了规划模块内部的RTKReplayPlanner类。在通过Dreamview调试规划模块时,经常发现该模块莫名其妙地退出,看日志文件没有任何可用信息,根据我的编程经验,这一定是我在某处的指针使用存在问题,要么是引用了空指针,要么是指针越界,如此等等,不一而足。是时候让核心转储文件发挥作用了。
我打开/apollo/data/core目录,果然找到了规划模块崩溃时生成的核心转储文件:core_planning.695,于是立刻在Docker内部(即使用bash docker/scripts/dev_into.sh命令进入Docker后的命令行终端内操作)借助GDB调试该文件,命令如下所示。注意:若需定位程序崩溃位置,必须在构建Apollo项目时,添加调试信息。也就是说,构建命令不能使用“build_opt”或“build_opt_gpu”等优化选项,而应使用“build”或“build_gpu”等带调试信息的选项。
gdb /apollo/bazel-bin/modules/planning/planning /apollo/data/core/core_planning.695 |
调试结果如下:
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1Copyright (C) 2014 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later |
上述结果表明,在bazel-out/local-dbg/genfiles/modules/common/proto/pnc_point.pb.h文件的1514行返回relative_time_时,this指针为空,即引用了一个空指针。显然,这里只是错误暴露处,而非错误产生处。联想到我修改了RTKReplayPlanner类,于是我立即在modules/planning/planner/rtk/rtk_replay_planner.cc中查找关键字:relative_time,找到相关代码处(注意:下面的代码是我修改后的内容,并非Apollo项目原有代码):
// reset relative time double zero_time = current_trajectory[matched_index].relative_time(); for (auto& trajectory_point : trajectory_points) { // davidhopper // We shoud add the "planning_init_point.relative_time()" to // maintain the correct time sequence. trajectory_point.set_relative_time(trajectory_point.relative_time() - zero_time + planning_init_point.relative_time()); |
最终结果水落石出,原来是double zero_time = current_trajectory[matched_index].relative_time();作了越界引用。找到了错误产生原因,代码修改方法也就比较容易了,对matched_index的范围作出限制即可解决问题。
自Apollo平台开放已来,我们收到了大量开发者的咨询和反馈,越来越多开发者基于Apollo擦出了更多的火花,并愿意将自己的成果贡献出来,这充分体现了Apollo『贡献越多,获得越多』的开源精神。为此我们开设了『开发者说』板块,希望开发者们能够踊跃投稿,更好地为广大自动驾驶开发者营造一个共享交流的平台!
全部0条评论
快来发表一下你的评论吧 !