openSUSE: Bugreport 应用程序崩溃
但报告错误并非“它不起作用”那么简单。延迟错误修复的因素之一是报告方式。因此,本指南应有助于改善开发人员和用户之间在错误解决方面的沟通。
良好错误报告的特点
因此请记住良好错误报告的两个特点
- 可重现:尝试在您自己的工作站上重现该错误。写下您重现该错误的每一步。有时,禁用后台任务可能有助于避免崩溃的应用程序与后台运行的另一个进程之间的交互。如果崩溃的应用程序与另一个进程交互,这也应报告为错误。您提供的每一个细节都有助于工程师在自己的工作站上重现该错误。
- 具体:不要在一条消息中报告多个错误。提供尽可能精确的信息。不要谈论其他可能迫使程序员或测试人员在理解您的消息之前进行解读的事情。
例如:假设有缺陷的应用程序是一个网络浏览器。每次您访问 example.org 时它都会崩溃,您想编写一份错误报告
BAD: My browser crashed. I think I was on www.example.org. I play golf with Mr. Big Boss, so you better fix this problem, or I'll report you to him. By the way, your Back icon looks UGGGLY!!! Thx 4 UR help.
GOOD: My browser "exact-name" crashed each time I went to www.example.org on an openSUSE-"exact-version" system.
EXCELLENT: My browser "exact-name"-"exact-version" crashed each time I went to www.example.org. I use the browser build dated 2012-May-23 from the openSUSE "Browsers" repository on an openSUSE-"exact-version" system. It crashed each time upon drawing the banner at the top of the page. I broke apart the page, and discovered that the following image link will crash it, unless you remove the "BORDER=0" attribute: <IMG SRC="banner.png" WIDTH="400" HEIGHT="60" BORDER="0" ALT="Banner">
分析问题
环境
首先,检查您的环境。写下系统版本、程序版本、插件(并附带它们的版本)等。之后,尝试重现问题并写下您使应用程序崩溃的步骤。找出问题是否通过新版本的软件得到解决:查看项目主页以获取更多信息。
安装 -debuginfo 包
大多数 GUI 应用程序在崩溃时会显示回溯。回溯能更准确地告诉开发人员错误发生的位置。通常使用 gdb 来创建此回溯。然而,当 gdb 具有调试信息时,它可以创建更详细的回溯。请为您安装应用程序的 -debuginfo 包,以改善 gdb 的输出。-debuginfo 包不属于 CD 套件,但您可以在完整网络安装源的 debug 目录中,在我们的 HTTP 下载服务器或任何镜像上找到它们(官方 openSUSE 版本的调试存储库。此目录可以在 YaST 中定义为安装源(作为您的 CD 的替代)。
请安装软件包并尝试重现崩溃。将您触发崩溃的操作描述和回溯附加到错误报告中。当您直接使用 gdb 时,这些软件包也有助于调试,请参阅下文。
调试
没有更新,您可以顺利重现问题。现在是时候开始调试应用程序了。调试可能需要很长时间——但有时这是获得良好错误报告并帮助开发人员快速解决错误的唯一方法。
调试应用程序的方法有很多种,因此您必须自行检查这些建议并为您的应用程序选择正确的形式。
strace
程序通常使用文件来获取配置信息、访问硬件或写入日志。有时,程序会错误地尝试访问这些文件。strace是一个有用的诊断、指导和调试工具,可以帮助处理这个问题。strace 跟踪系统调用(因此得名),其中包括使用内存和文件的调用。
在最简单的情况下,strace 会运行指定的命令直到它退出。它会拦截并记录进程调用的系统调用以及进程接收到的信号。每个系统调用的名称、其参数及其返回值将打印到标准错误或通过 -o 选项指定的文件。
strace 命令的示例cat /dev/null是
strace -f -ttt -o strace.log cat /dev/null
这将创建一个名为strace.log的文件,在当前目录中。我们检查文件,其相关部分如下所示
open("/dev/null", O_RDONLY) = 3
错误(通常返回值为 -1)会附加 errno 符号和错误字符串。
open("/foo/bar", O_RDONLY) = -1 ENOENT (No such file or directory)
因此 strace 似乎是调试在启动时崩溃或在打开或保存文件时崩溃的应用程序的最佳方法。
使用 GDB
GDB,GNU 调试器,是一个用于查找通常涉及内存损坏的运行时错误的程序。您还可以使用它来获取所谓的回溯,即导致崩溃的函数调用序列。当您想从/usr/bin/brokenprogram获取回溯时,该程序每次运行时都会抛出段错误,您可以使用以下命令
$ gdb /usr/bin/brokenprogram GNU gdb 6.5 ... (gdb)
现在您处于 GDB 自己的提示符下。如果您仍然缺少 -debuginfo 包,gdb 将显示类似以下的输出
Missing separate debuginfo for /lib64/libm.so.6 Try: zypper install -C "debuginfo(build-id)=35d35d9ce781be3a140a34242d998498615b021f" Missing separate debuginfo for /lib64/libpthread.so.0 Try: zypper install -C "debuginfo(build-id)=522229c2dde70aaa8e4295ecb7b6643c810f758f" Missing separate debuginfo for /lib64/libc.so.6 Try: zypper install -C "debuginfo(build-id)=c3e668c7a2e7ae513e801d34a968a43510b29b52"
只需将给定的 zypper 命令复制并粘贴到另一个终端中以安装缺失的包,然后使用quit退出 gdb 并重新开始。
在 gdb 中输入 run 并等待程序崩溃
(gdb) run Starting program: /usr/bin/brokenprogram Program received signal SIGSEGV, Segmentation fault. 0x08048394 in brokenfunc () at brokenprogram.c:4 4 *i = 2; (gdb)
或者,如果程序在登录时运行,由 init 脚本或类似的东西启动,您可以附加到正在运行的进程,然后使用continue继续执行,然后等待程序崩溃
$ pidof brokenprogram 12345 $ gdb /usr/bin/brokenprogram 12345 (gdb) continue Continuing. Program received signal SIGSEGV, Segmentation fault. 0x08048394 in brokenfunc () at broken.c:4 4 *i = 2; (gdb)
好了,回到 GDB 提示符下,启用日志记录并使用thread apply all backtrace full.
(gdb) set logging on
Copying output to gdb.txt.
(gdb) thread apply all backtrace full
[New Thread 0x7ffff6d0b700 (LWP 4520)]
Thread 2 (Thread 0x7ffff750c700 (LWP 4519)):
#0 0x00007ffff7542849 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
resultvar = 0
pid = 4515
selftid = 4519
#1 0x00007ffff7543cd8 in __GI_abort () at abort.c:89
save_stage = 2
act = {__sigaction_handler = {sa_handler = 0x6466203030303030, sa_sigaction = 0x6466203030303030}, sa_mask = {__val = {3906931166148702266, 2314885530818459703, 2314885530818453536,
3395749441387372576, 7596498572764408172, 3330465998920576354, 7378697628689264499, 3256155514972955191, 7233967814408037943, 3255307721929404514, 3472891250476064880, 3616454703663034416,
2321666313920262688, 2314885530818453536, 2314885530818453536, 8096}}, sa_flags = 89, sa_restorer = 0x7ffff750bed0}
sigs = {__val = {32, 0 <repeats 15 times>}}
#2 0x00007ffff7581114 in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7ffff76770e0 "*** Error in `%s': %s: 0x%s ***\n") at ../sysdeps/posix/libc_fatal.c:175
ap =
fd = 3
on_2 = <optimized out>
list = <optimized out>
nlist = <optimized out>
cp = <optimized out>
written = <optimized out>
#3 0x00007ffff758696e in malloc_printerr (action=3, str=0x7ffff767317b "free(): invalid pointer", ptr=<optimized out>) at malloc.c:4916
buf = '0' <repeats 11 times>, "12345"
cp = <optimized out>
#4 0x0000000000400a88 in work (t=0x0) at brokenprogram.c:18
i = 10
tid = 0
result = -4.943676956758269
#5 0x00007ffff78c40db in start_thread (arg=0x7ffff750c700) at pthread_create.c:309
__res = <optimized out>
pd = 0x7ffff750c700
now = <optimized out>
unwind_buf = {cancel_jmp_buf = {{jmp_buf = {140737342654208, 3582659488515194238, 1, 140737354125312, 0, 140737342654208, -3582675678410998402, -3582676406767878786}, mask_was_saved = 0}}, priv = {
pad = {0x0, 0x0, 0x0, 0x0}, data = {prev = 0x0, cleanup = 0x0, canceltype = 0}}}
not_first_call = <optimized out>
pagesize_m1 = <optimized out>
pd = 0x7ffff750c700
now = <optimized out>
unwind_buf = {cancel_jmp_buf = {{jmp_buf = {140737342654208, 3582659488515194238, 1, 140737354125312, 0, 140737342654208, -3582675678410998402, -3582676406767878786}, mask_was_saved = 0}}, priv = {
pad = {0x0, 0x0, 0x0, 0x0}, data = {prev = 0x0, cleanup = 0x0, canceltype = 0}}}
not_first_call = <optimized out>
pagesize_m1 = <optimized out>
sp = <optimized out>
freesize = <optimized out>
__PRETTY_FUNCTION__ = "start_thread"
#6 0x00007ffff75f47cd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111
No locals.
Thread 1 (Thread 0x7ffff7fe1700 (LWP 4515)):
[...]
(gdb)
获取回溯。通过输入quit退出 GDB,回溯将保存到文件gdb.txt中,位于当前目录。
如果程序仅在您使用特定参数时崩溃,例如--crash,则必须将该参数添加到run命令
(gdb) run --crash Starting program: /usr/bin/brokenprogram --crash
dmesg
dmesg 打印或控制内核环形缓冲区。因此,如果您遇到内核本身或与内核相关的应用程序(特别是内核模块或热插拔设备)的问题,您应该使用此工具。