gdb如何比较core文件和image及buildid
gdb
从git上看到的提交记录,关键的修改是在elf_core_file_matches_executable_p函数中添加的对于build_id的比较。
///@file: gdb-10.1\bfd\elfcore.h
bfd_boolean
elf_core_file_matches_executable_p (bfd *core_bfd, bfd *exec_bfd)
{
char* corename;
/* xvecs must match if both are ELF files for the same target. */
if (core_bfd->xvec != exec_bfd->xvec)
{
bfd_set_error (bfd_error_system_call);
return FALSE;
}
/* If both BFDs have identical build-ids, then they match. */
if (core_bfd->build_id != NULL
&& exec_bfd->build_id != NULL
&& core_bfd->build_id->size == exec_bfd->build_id->size
&& memcmp (core_bfd->build_id->data, exec_bfd->build_id->data,
core_bfd->build_id->size) == 0)
return TRUE;
/* See if the name in the corefile matches the executable name. */
corename = elf_tdata (core_bfd)->core->program;
if (corename != NULL)
{
const char* execname = strrchr (bfd_get_filename (exec_bfd), '/');
execname = execname ? execname + 1 : bfd_get_filename (exec_bfd);
if (strcmp (execname, corename) != 0)
return FALSE;
}
return TRUE;
}
buildid
直观上看,这个信息应该是由内核在生成coredump文件的时候生成的,但是在内核的代码中并没有找到对于这个buildid的特殊处理(关键字是NT_GNU_BUILD_ID)。好在结合本次提交信息可以很快找到对应的实现:就是从coredump文件中包含的文件映射信息中读取的,读取的方法也是遍历文件header中的所有note信息,然后找到其中包含NT_GNU_BUILD_ID的节。由于这份数据是在生成coredump文件的时候从内存中生成的一个拷贝,所以可以具有记录信息。
static bfd_boolean
elfobj_grok_gnu_note (bfd *abfd, Elf_Internal_Note *note)
{
switch (note->type)
{
default:
return TRUE;
case NT_GNU_PROPERTY_TYPE_0:
return _bfd_elf_parse_gnu_properties (abfd, note);
case NT_GNU_BUILD_ID:
return elfobj_grok_gnu_build_id (abfd, note);
}
}
static bfd_boolean
elf_parse_notes (bfd *abfd, char *buf, size_t size, file_ptr offset,
size_t align)
{
///...
case bfd_core:
{
#define GROKER_ELEMENT(S,F) {S, sizeof (S) - 1, F}
struct
{
const char * string;
size_t len;
bfd_boolean (* func)(bfd *, Elf_Internal_Note *);
}
grokers[] =
{
GROKER_ELEMENT ("", elfcore_grok_note),
GROKER_ELEMENT ("FreeBSD", elfcore_grok_freebsd_note),
GROKER_ELEMENT ("NetBSD-CORE", elfcore_grok_netbsd_note),
GROKER_ELEMENT ( "OpenBSD", elfcore_grok_openbsd_note),
GROKER_ELEMENT ("QNX", elfcore_grok_nto_note),
GROKER_ELEMENT ("SPU/", elfcore_grok_spu_note),
GROKER_ELEMENT ("GNU", elfobj_grok_gnu_note)
};
#undef GROKER_ELEMENT
int i;
for (i = ARRAY_SIZE (grokers); i--;)
{
if (in.namesz >= grokers[i].len
&& strncmp (in.namedata, grokers[i].string,
grokers[i].len) == 0)
{
if (! grokers[i].func (abfd, & in))
return FALSE;
break;
}
}
break;
}
///...
}
在linux下,可以通过readelf命令查看可执行文件的BUILD_ID,
tsecer@harry: readelf -n a.out
Displaying notes found at file offset 0x00000274 with length 0x00000024:
所有者 Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 296dc8433375f102ba8f0e3b766fec0cb12cc170
note
从内核填充note的代码可以看到,note首先是通过字符串表示属于哪个大的范围,之后还有一个数值表示属于哪个小类,最后通过长度+内容表示文件内容
static void fill_note(struct memelfnote *note, const char *name, int type,
unsigned int sz, void *data)
{
note->name = name;
note->type = type;
note->datasz = sz;
note->data = data;
return;
}
结合gdb的代码可以看到,GNU扩展的note为一类,该类中当前包含了一些信息,其中就包含了我们关心的NT_GNU_BUILD_ID字段
/* Values for notes in non-core files using name "GNU". */
#define NT_GNU_ABI_TAG 1
#define NT_GNU_HWCAP 2 /* Used by ld.so and kernel vDSO. */
#define NT_GNU_BUILD_ID 3 /* Generated by ld --build-id. */
#define NT_GNU_GOLD_VERSION 4 /* Generated by gold. */
#define NT_GNU_PROPERTY_TYPE_0 5 /* Generated by gcc. */
#define NT_GNU_BUILD_ATTRIBUTE_OPEN 0x100
#define NT_GNU_BUILD_ATTRIBUTE_FUNC 0x101
perf
从perf的调用链可以看到,在perf的session结束之后,还是会记录所有buildid信息,并且还会缓存文件。
(gdb) bt
#0 write_buildid (fd=3, misc=1, pid=-1, build_id=0x7f1ddb "\026ٸ\351\264\325\062\223\330\001t谢k\341˾Ω", name_len=18, name=<optimized out>) at util/header.c:223
#1 __dsos__write_buildid_table (head=head@entry=0x7f1b18, pid=-1, misc=1, fd=fd@entry=3) at util/header.c:255
#2 0x00000000004738db in machine__write_buildid_table (fd=3, machine=0x7f1ac0) at util/header.c:275
#3 dsos__write_buildid_table (fd=3, header=0x7f19d0) at util/header.c:288
#4 write_build_id (fd=fd@entry=3, h=h@entry=0x7f19d0, evlist=evlist@entry=0x7f0d40) at util/header.c:497
#5 0x0000000000474612 in do_write_feat (evlist=0x7f0d40, p=<synthetic pointer>, type=2, h=0x7f19d0, fd=3) at util/header.c:2225
#6 perf_header__adds_write (fd=3, evlist=0x7f0d40, header=0x7f19d0) at util/header.c:2264
#7 perf_session__write_header (session=0x7f19d0, evlist=0x7f0d40, fd=3, at_exit=at_exit@entry=true) at util/header.c:2361
#8 0x000000000042cd8a in perf_record__exit (status=<optimized out>, arg=0x71ab00 <record>) at builtin-record.c:317
#9 0x00007ffff5b21a49 in __run_exit_handlers () from /lib64/libc.so.6
#10 0x00007ffff5b21ab5 in exit () from /lib64/libc.so.6
#11 0x0000000000416760 in handle_internal_command (argv=0x7fffffffe380, argc=4) at perf.c:376
#12 run_argv (argv=0x7fffffffe180, argcp=0x7fffffffe18c) at perf.c:420
#13 main (argc=4, argv=0x7fffffffe380) at perf.c:521
(gdb)
static int perf_session__cache_build_ids(struct perf_session *session)
{
struct rb_node *nd;
int ret;
char debugdir[PATH_MAX];
snprintf(debugdir, sizeof(debugdir), "%s", buildid_dir);
if (mkdir(debugdir, 0755) != 0 && errno != EEXIST)
return -1;
ret = machine__cache_build_ids(&session->machines.host, debugdir);
for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
struct machine *pos = rb_entry(nd, struct machine, rb_node);
ret |= machine__cache_build_ids(pos, debugdir);
}
return ret ? -1 : 0;
}
int build_id_cache__add_s(const char *sbuild_id, const char *debugdir,
const char *name, bool is_kallsyms, bool is_vdso)
{
///...
len = scnprintf(filename, size, "%s%s%s",
debugdir, slash ? "/" : "",
is_vdso ? VDSO__MAP_NAME : realname);
if (mkdir_p(filename, 0755))
goto out_free;
snprintf(filename + len, size - len, "/%s", sbuild_id);
if (access(filename, F_OK)) {
if (is_kallsyms) {
if (copyfile("/proc/kallsyms", filename))
goto out_free;
} else if (link(realname, filename) && copyfile(name, filename))
goto out_free;
}
len = scnprintf(linkname, size, "%s/.build-id/%.2s",
debugdir, sbuild_id);
///...
}
perf为什么要保存这些二进制呢?在perf的一个wiki文档中的一个说明可能有一定意义:也就是为了在系统中保存多个同名文件的备份,这样文件被重新编译之后,还可以使用perf数据生成时使用的二进制文件。
Given that build-id are immutable, they uniquely identify a binary. If a binary is recompiled, a new build-id is generated and a new copy of the ELF images is saved in the cache.
在查找符号的过程中,是按照这个顺序逐个尝试的,并且后面尝试成功将会覆盖前面尝试成功的内容,或者说:越靠后的位置读取到的文件优先级越高。
static enum dso_binary_type binary_type_symtab[] = {
DSO_BINARY_TYPE__KALLSYMS,
DSO_BINARY_TYPE__GUEST_KALLSYMS,
DSO_BINARY_TYPE__JAVA_JIT,
DSO_BINARY_TYPE__DEBUGLINK,
DSO_BINARY_TYPE__BUILD_ID_CACHE,
DSO_BINARY_TYPE__FEDORA_DEBUGINFO,
DSO_BINARY_TYPE__UBUNTU_DEBUGINFO,
DSO_BINARY_TYPE__BUILDID_DEBUGINFO,
DSO_BINARY_TYPE__SYSTEM_PATH_DSO,
DSO_BINARY_TYPE__GUEST_KMODULE,
DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE,
DSO_BINARY_TYPE__NOT_FOUND,
};
int dso__load(struct dso *dso, struct map *map, symbol_filter_t filter)
{
///...
/* Iterate over candidate debug images.
* Keep track of "interesting" ones (those which have a symtab, dynsym,
* and/or opd section) for processing.
*/
for (i = 0; i < DSO_BINARY_TYPE__SYMTAB_CNT; i++) {
struct symsrc *ss = &ss_[ss_pos];
bool next_slot = false;
enum dso_binary_type symtab_type = binary_type_symtab[i];
if (dso__binary_type_file(dso, symtab_type,
root_dir, name, PATH_MAX))
continue;
///...
}
补充
gdb比较buildid的修改在2019-10-30 Keith Seitz 提交,通过gdb的发布历史,可以看到在此次提交之后最近的一次发布是gdb的9.1版本。
Release Estimate Schedule Actual Slip (months) Comment
9.1 2019-12 2019-12 2020-02-08 1.5 Large number of issues to investigate
8.3.1 2019-08 2019-09 2019-09-20 0.5 None