如何在gdb中再次显示core的摘要信息
问题
在使用gdb加载core文件开始的时候会显示导致core的直接原因,但是随着分析的进行,gdb的输出会越来越多,如果想回过头来再次(again)确认下这些导致core的信息,此时有没有什么命令可以把加载core文件时的信息再输出一遍?
when loading a core dump into GDB the reason why it crashed automatically is displayed. For example
Program terminated with signal 11, Segmentation fault.
Is there any way to get the information again? The thing is, that I'm writing a script which needs this information. But if the signal is only available after loading the core dump amd I can't access the information later on.
Is there really no command for such an important feature?
gdb的显示core信息的代码
从gdb的代码来看,这个信息只有在打开core文件的时候才会显示一次;或者说这个输出不是一个函数,从代码上没有办法在启动之后的时候再次重现。
void
core_target_open (const char *arg, int from_tty)
{
///....
p = bfd_core_file_failing_command (core_bfd);
if (p)
printf_filtered (_("Core was generated by `%s'.\n"), p);
/* Clearing any previous state of convenience variables. */
clear_exit_convenience_vars ();
siggy = bfd_core_file_failing_signal (core_bfd);
if (siggy > 0)
{
gdbarch *core_gdbarch = target->core_gdbarch ();
/* If we don't have a CORE_GDBARCH to work with, assume a native
core (map gdb_signal from host signals). If we do have
CORE_GDBARCH to work with, but no gdb_signal_from_target
implementation for that gdbarch, as a fallback measure,
assume the host signal mapping. It'll be correct for native
cores, but most likely incorrect for cross-cores. */
enum gdb_signal sig = (core_gdbarch != NULL
&& gdbarch_gdb_signal_from_target_p (core_gdbarch)
? gdbarch_gdb_signal_from_target (core_gdbarch,
siggy)
: gdb_signal_from_host (siggy));
printf_filtered (_("Program terminated with signal %s, %s"),
gdb_signal_to_name (sig), gdb_signal_to_string (sig));
if (gdbarch_report_signal_info_p (core_gdbarch))
gdbarch_report_signal_info (core_gdbarch, current_uiout, sig);
printf_filtered (_(".\n"));
/* Set the value of the internal variable $_exitsignal,
which holds the signal uncaught by the inferior. */
set_internalvar_integer (lookup_internalvar ("_exitsignal"),
siggy);
}
///....
}
gdb从哪里提取signal信息
从代码上看,gdb获取的singal信息是通过elfcore_grok_prstatus函数生成的,这个grok在gcc代码中的出镜率也很高,该单词表示"understand (something) intuitively or by empathy.",大致对应中文的"设身处地的理解别人的意图",所以这个单词在gcc中更加合适,因为毕竟gcc就是为了把程序语言翻译为机器语言,在这个过程中必然少不了理解程序的真实意图。对应到gdb的场景,这个地方的理解主要是将内核dump出来的进程状态信息转换为调试者(debugger)可以理解形式。
对于我们最常见的内存访问问题来说,我们最关心的就是触发异常的信号和异常访问的地址,当然,还有进程使用的可执行文件以及进程启动时的命令行参数信息。从gdb的代码可以看到,这些关键信息是从core文件中的prstatus(program status)结构中获取的。
///@file: gdb-10.1\bfd\elf.c
#if defined (HAVE_PRSTATUS_T)
static bfd_boolean
elfcore_grok_prstatus (bfd *abfd, Elf_Internal_Note *note)
{
size_t size;
int offset;
if (note->descsz == sizeof (prstatus_t))
{
prstatus_t prstat;
size = sizeof (prstat.pr_reg);
offset = offsetof (prstatus_t, pr_reg);
memcpy (&prstat, note->descdata, sizeof (prstat));
/* Do not overwrite the core signal if it
has already been set by another thread. */
if (elf_tdata (abfd)->core->signal == 0)
elf_tdata (abfd)->core->signal = prstat.pr_cursig;
if (elf_tdata (abfd)->core->pid == 0)
elf_tdata (abfd)->core->pid = prstat.pr_pid;
/* pr_who exists on:
solaris 2.5+
unixware 4.2
pr_who doesn't exist on:
linux 2.[01]
*/
#if defined (HAVE_PRSTATUS_T_PR_WHO)
elf_tdata (abfd)->core->lwpid = prstat.pr_who;
#else
elf_tdata (abfd)->core->lwpid = prstat.pr_pid;
#endif
}
#if defined (HAVE_PRSTATUS32_T)
else if (note->descsz == sizeof (prstatus32_t))
{
/* 64-bit host, 32-bit corefile */
prstatus32_t prstat;
size = sizeof (prstat.pr_reg);
offset = offsetof (prstatus32_t, pr_reg);
memcpy (&prstat, note->descdata, sizeof (prstat));
/* Do not overwrite the core signal if it
has already been set by another thread. */
if (elf_tdata (abfd)->core->signal == 0)
elf_tdata (abfd)->core->signal = prstat.pr_cursig;
if (elf_tdata (abfd)->core->pid == 0)
elf_tdata (abfd)->core->pid = prstat.pr_pid;
/* pr_who exists on:
solaris 2.5+
unixware 4.2
pr_who doesn't exist on:
linux 2.[01]
*/
#if defined (HAVE_PRSTATUS32_T_PR_WHO)
elf_tdata (abfd)->core->lwpid = prstat.pr_who;
#else
elf_tdata (abfd)->core->lwpid = prstat.pr_pid;
#endif
}
#endif /* HAVE_PRSTATUS32_T */
else
{
/* Fail - we don't know how to handle any other
note size (ie. data object type). */
return TRUE;
}
/* Make a ".reg/999" section and a ".reg" section. */
return _bfd_elfcore_make_pseudosection (abfd, ".reg",
size, note->descpos + offset);
}
#endif /* defined (HAVE_PRSTATUS_T) */
static bfd_boolean
elfcore_grok_psinfo (bfd *abfd, Elf_Internal_Note *note)
{
if (note->descsz == sizeof (elfcore_psinfo_t))
{
elfcore_psinfo_t psinfo;
memcpy (&psinfo, note->descdata, sizeof (psinfo));
#if defined (HAVE_PSINFO_T_PR_PID) || defined (HAVE_PRPSINFO_T_PR_PID)
elf_tdata (abfd)->core->pid = psinfo.pr_pid;
#endif
elf_tdata (abfd)->core->program
= _bfd_elfcore_strndup (abfd, psinfo.pr_fname,
sizeof (psinfo.pr_fname));
elf_tdata (abfd)->core->command
= _bfd_elfcore_strndup (abfd, psinfo.pr_psargs,
sizeof (psinfo.pr_psargs));
}
#if defined (HAVE_PRPSINFO32_T) || defined (HAVE_PSINFO32_T)
else if (note->descsz == sizeof (elfcore_psinfo32_t))
{
/* 64-bit host, 32-bit corefile */
elfcore_psinfo32_t psinfo;
memcpy (&psinfo, note->descdata, sizeof (psinfo));
#if defined (HAVE_PSINFO32_T_PR_PID) || defined (HAVE_PRPSINFO32_T_PR_PID)
elf_tdata (abfd)->core->pid = psinfo.pr_pid;
#endif
elf_tdata (abfd)->core->program
= _bfd_elfcore_strndup (abfd, psinfo.pr_fname,
sizeof (psinfo.pr_fname));
elf_tdata (abfd)->core->command
= _bfd_elfcore_strndup (abfd, psinfo.pr_psargs,
sizeof (psinfo.pr_psargs));
}
#endif
else
{
/* Fail - we don't know how to handle any other
note size (ie. data object type). */
return TRUE;
}
/* Note that for some reason, a spurious space is tacked
onto the end of the args in some (at least one anyway)
implementations, so strip it off if it exists. */
{
char *command = elf_tdata (abfd)->core->command;
int n = strlen (command);
if (0 < n && command[n - 1] == ' ')
command[n - 1] = '\0';
}
return TRUE;
}
prstatus信息的填充
从内核代码中可以看到,gdb显示的信号就是内核填充的信号。那么是不是所有的coredump文件都有信号呢?内核生成的coredump文件的确如此,因为在内核中只有信号才会触发coredump文件生成。不过因为gdb可以随时生成被调试进程的core文件,所以不是每个core文件都有合法的信号信息。
从数据结构上看,core文件中记录的进程信息只有80个字节,如果命令行太长通过coredump文件显示就不全。
///@file: linux-3.12.6\include\uapi\linux\elfcore.h
struct elf_siginfo
{
int si_signo; /* signal number */
int si_code; /* extra code */
int si_errno; /* errno */
};
/*
* Definitions to generate Intel SVR4-like core files.
* These mostly have the same names as the SVR4 types with "elf_"
* tacked on the front to prevent clashes with linux definitions,
* and the typedef forms have been avoided. This is mostly like
* the SVR4 structure, but more Linuxy, with things that Linux does
* not support and which gdb doesn't really use excluded.
* Fields present but not used are marked with "XXX".
*/
struct elf_prstatus
{
#if 0
long pr_flags; /* XXX Process flags */
short pr_why; /* XXX Reason for process halt */
short pr_what; /* XXX More detailed reason */
#endif
struct elf_siginfo pr_info; /* Info associated with signal */
short pr_cursig; /* Current signal */
unsigned long pr_sigpend; /* Set of pending signals */
unsigned long pr_sighold; /* Set of held signals */
#if 0
struct sigaltstack pr_altstack; /* Alternate stack info */
struct sigaction pr_action; /* Signal action for current sig */
#endif
pid_t pr_pid;
pid_t pr_ppid;
pid_t pr_pgrp;
pid_t pr_sid;
struct timeval pr_utime; /* User time */
struct timeval pr_stime; /* System time */
struct timeval pr_cutime; /* Cumulative user time */
struct timeval pr_cstime; /* Cumulative system time */
#if 0
long pr_instr; /* Current instruction */
#endif
elf_gregset_t pr_reg; /* GP registers */
#ifdef CONFIG_BINFMT_ELF_FDPIC
/* When using FDPIC, the loadmap addresses need to be communicated
* to GDB in order for GDB to do the necessary relocations. The
* fields (below) used to communicate this information are placed
* immediately after ``pr_reg'', so that the loadmap addresses may
* be viewed as part of the register set if so desired.
*/
unsigned long pr_exec_fdpic_loadmap;
unsigned long pr_interp_fdpic_loadmap;
#endif
int pr_fpvalid; /* True if math co-processor being used. */
};
///@file: linux-3.12.6\fs\binfmt_elf.c
static int fill_note_info(struct elfhdr *elf, int phdrs,
struct elf_note_info *info,
siginfo_t *siginfo, struct pt_regs *regs)
{
///....
fill_prstatus(info->prstatus, current, siginfo->si_signo);
///....
}
///@file: linux-3.12.6\include\uapi\linux\elfcore.h
#define ELF_PRARGSZ (80) /* Number of chars for args */
struct elf_prpsinfo
{
char pr_state; /* numeric process state */
char pr_sname; /* char for pr_state */
char pr_zomb; /* zombie */
char pr_nice; /* nice val */
unsigned long pr_flag; /* flags */
__kernel_uid_t pr_uid;
__kernel_gid_t pr_gid;
pid_t pr_pid, pr_ppid, pr_pgrp, pr_sid;
/* Lots missing */
char pr_fname[16]; /* filename of executable */
char pr_psargs[ELF_PRARGSZ]; /* initial part of arg list */
};
gdb中的一段注释
在gdb的源代码中,有一段关于coredump文件比较精辟的描述:简言之,core文件就是一个标准的ELF格式文件,只不过是以执行(即进程运行过程中)的角度观察到的视图,而不是链接器看到的静态文件视图。当然,反过来说,core文件中必然包含了进程特有的、而可执行文件不可能有的信息:例如寄存器状态这些信息在一个静态的可执行文件中必然是不存在的。虽然这些看起来比较特殊,但是并不意味着它们不可以放在efl文件格式中。正如调试器使用的DWARF格式一样,ELF格式也具有良好的扩展性和兼容性,所以这些自定义格式的运行时信息同样可以放入core文件中。再直接一点说,就是通过在ELF文件中定义专用的、特殊的NOTE节来存储,这些节可以使用约定的、特殊的节名来进行标识和区分。
///@file: gdb-10.1\bfd\elfcore.h
/* Core files are simply standard ELF formatted files that partition
the file using the execution view of the file (program header table)
rather than the linking view. In fact, there is no section header
table in a core file.
The process status information (including the contents of the general
register set) and the floating point register set are stored in a
segment of type PT_NOTE. We handcraft a couple of extra bfd sections
that allow standard bfd access to the general registers (.reg) and the
floating point registers (.reg2). */
如何随时显示导致core的信号信息
在本文的开始也说明了stackoverflow上给出的解决方案,就是通过gdb的Convenience Variables(伴手变量?)gdb官方文档说明,也就是gdb内置的变量可以看到这个信号信息。
至于命令行参数信息,在core文件中依然可以通过info proc来显示完整的命令行参数(当然也可以通过info reg查看寄存器信息)。
栗子
通过$_siginfo(注意前面的$引导符)可以看到信号描述,其中有信号数值(si_signo = 11)和地址信息(_sigfault = {si_addr = 0x1),但是info proc同样只是保存了最多80个字符,而完整的命令行信息在core文件中并没有保存,所以完整的信息需要从main函数的argv中找。
tsecer@harry: cat -n gdb.core.info.recurr.cpp
1 int main(int argc, const char *argv[])
2 {
3 return *(int*)(0 + 1) = 0;
4 }
tsecer@harry: gcc -g gdb.core.info.recurr.cpp
tsecer@harry: ./a.out 1 2 3 43 5 4 5 46 5 757 6 7 68 7 87 9 8 9 8 98 0 d f ds fw as dfs f sd sg s gs
段错误 (core dumped)
tsecer@harry: gdb -c core.11622 --quiet
[New LWP 11622]
Core was generated by `./a.out 1 2 3 43 5 4 5 46 5 757 6 7 68 7 87 9 8 9 8 98 0 d f ds fw as dfs f sd'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004004e7 in ?? ()
(gdb) info proc
exe = './a.out 1 2 3 43 5 4 5 46 5 757 6 7 68 7 87 9 8 9 8 98 0 d f ds fw as dfs f sd'
(gdb) p $_siginfo
$1 = {si_signo = 11, si_errno = 0, si_code = 1, _sifields = {_pad = {1, 0 <repeats 27 times>}, _kill = {si_pid = 1, si_uid = 0}, _timer = {si_tid = 1, si_overrun = 0, si_sigval = {
sival_int = 0, sival_ptr = 0x0}}, _rt = {si_pid = 1, si_uid = 0, si_sigval = {sival_int = 0, sival_ptr = 0x0}}, _sigchld = {si_pid = 1, si_uid = 0, si_status = 0, si_utime = 0,
si_stime = 0}, _sigfault = {si_addr = 0x1, _addr_lsb = 0, _addr_bnd = {_lower = 0x0, _upper = 0x0}}, _sigpoll = {si_band = 1, si_fd = 0}}}
(gdb) p *argv@argc
$4 = {0x7fffe28f15fc "./a.out", 0x7fffe28f1604 "1", 0x7fffe28f1606 "2", 0x7fffe28f1608 "3", 0x7fffe28f160a "43", 0x7fffe28f160d "5", 0x7fffe28f160f "4", 0x7fffe28f1611 "5",
0x7fffe28f1613 "46", 0x7fffe28f1616 "5", 0x7fffe28f1618 "757", 0x7fffe28f161c "6", 0x7fffe28f161e "7", 0x7fffe28f1620 "68", 0x7fffe28f1623 "7", 0x7fffe28f1625 "87", 0x7fffe28f1628 "9",
0x7fffe28f162a "8", 0x7fffe28f162c "9", 0x7fffe28f162e "8", 0x7fffe28f1630 "98", 0x7fffe28f1633 "0", 0x7fffe28f1635 "d", 0x7fffe28f1637 "f", 0x7fffe28f1639 "ds", 0x7fffe28f163c "fw",
0x7fffe28f163f "as", 0x7fffe28f1642 "dfs", 0x7fffe28f1646 "f", 0x7fffe28f1648 "sd", 0x7fffe28f164b "sg", 0x7fffe28f164e "s", 0x7fffe28f1650 "gs"}
(gdb)