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

posted on 2023-05-15 20:06  tsecer  阅读(155)  评论(0编辑  收藏  举报

导航