// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

#include <assert.h>
#include <byteswap.h>
#include <elf.h>
#include <elfutils/libdw.h>
#include <elfutils/libdwelf.h>
#include <elfutils/version.h>
#include <errno.h>
#include <fcntl.h>
#include <gelf.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "binary_buffer.h"
#include "cleanup.h"
#include "debug_info.h"
#include "elf_file.h"
#include "elf_notes.h"
#include "error.h"
#include "linux_kernel.h"
#include "openmp.h"
#include "platform.h"
#include "program.h"
#include "util.h"

static inline Dwarf *drgn_elf_file_dwarf_key(struct drgn_elf_file * const *entry)
{
	return (*entry)->dwarf;
}
DEFINE_HASH_TABLE_FUNCTIONS(drgn_elf_file_dwarf_table, drgn_elf_file_dwarf_key,
			    ptr_key_hash_pair, scalar_key_eq);
DEFINE_VECTOR_FUNCTIONS(drgn_module_vector);

struct drgn_module_key {
	const void *build_id;
	size_t build_id_len;
	uint64_t start, end;
};

static inline struct drgn_module_key
drgn_module_key(struct drgn_module * const *entry)
{
	return (struct drgn_module_key){
		.build_id = (*entry)->build_id,
		.build_id_len = (*entry)->build_id_len,
		.start = (*entry)->start,
		.end = (*entry)->end,
	};
}

static inline struct hash_pair
drgn_module_key_hash_pair(const struct drgn_module_key *key)
{
	size_t hash = hash_bytes(key->build_id, key->build_id_len);
	hash = hash_combine(hash, key->start);
	hash = hash_combine(hash, key->end);
	return hash_pair_from_avalanching_hash(hash);
}
static inline bool drgn_module_key_eq(const struct drgn_module_key *a,
				      const struct drgn_module_key *b)
{
	return (a->build_id_len == b->build_id_len &&
		memcmp(a->build_id, b->build_id, a->build_id_len) == 0 &&
		a->start == b->start && a->end == b->end);
}
DEFINE_HASH_TABLE_FUNCTIONS(drgn_module_table, drgn_module_key,
			    drgn_module_key_hash_pair, drgn_module_key_eq);

DEFINE_HASH_SET_FUNCTIONS(c_string_set, c_string_key_hash_pair,
			  c_string_key_eq);

/**
 * @c Dwfl_Callbacks::find_elf() implementation.
 *
 * If the ELF file was reported directly, this returns it. Otherwise, it falls
 * back to an appropriate callback.
 *
 * Ideally we'd use @c dwfl_report_elf() instead, but that doesn't take an @c
 * Elf handle, which we need for a couple of reasons:
 *
 * - We usually already have the @c Elf handle open in order to identify the
 *   file.
 * - For kernel modules, we set the section addresses in the @c Elf handle
 *   ourselves instead of using @c Dwfl_Callbacks::section_address().
 *
 * Additionally, there's a special case for vmlinux. It is usually an @c ET_EXEC
 * ELF file, but when KASLR is enabled, it needs to be handled like an @c ET_DYN
 * file. libdwfl has a hack for this when @c dwfl_report_module() is used, but
 * @ref dwfl_report_elf() bypasses this hack.
 *
 * So, we're stuck using @c dwfl_report_module() and this dummy callback.
 */
static int drgn_dwfl_find_elf(Dwfl_Module *dwfl_module, void **userdatap,
			      const char *name, Dwarf_Addr base,
			      char **file_name, Elf **elfp)
{
	struct drgn_module *module = *userdatap;
	if (module->elf) {
		*file_name = module->path;
		int fd = module->fd;
		*elfp = module->elf;
		// libdwfl consumes the returned path, file descriptor, and ELF
		// handle, so clear the fields.
		module->path = NULL;
		module->fd = -1;
		module->elf = NULL;
		return fd;
	}
	if (module->prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) {
		*elfp = NULL;
		return -1;
	} else if (module->prog->flags & DRGN_PROGRAM_IS_LIVE) {
		return dwfl_linux_proc_find_elf(dwfl_module, userdatap, name,
						base, file_name, elfp);
	} else {
		return dwfl_build_id_find_elf(dwfl_module, userdatap, name,
					      base, file_name, elfp);
	}
}

/**
 * @c Dwfl_Callbacks::section_address() implementation.
 *
 * We set the section header @c sh_addr in memory instead of using this, but
 * libdwfl requires the callback pointer to be non-@c NULL. It will be called
 * for any sections that still have a zero @c sh_addr, meaning they are not
 * present in memory.
 */
static int drgn_dwfl_section_address(Dwfl_Module *module, void **userdatap,
				     const char *name, Dwarf_Addr base,
				     const char *secname, Elf32_Word shndx,
				     const GElf_Shdr *shdr, Dwarf_Addr *addr)
{
	*addr = -1;
	return DWARF_CB_OK;
}

static const Dwfl_Callbacks drgn_dwfl_callbacks = {
	.find_elf = drgn_dwfl_find_elf,
	.find_debuginfo = dwfl_standard_find_debuginfo,
	.section_address = drgn_dwfl_section_address,
};

static void drgn_module_destroy(struct drgn_module *module)
{
	if (module) {
		drgn_error_destroy(module->err);
		drgn_module_orc_info_deinit(module);
		drgn_module_dwarf_info_deinit(module);
		elf_end(module->elf);
		if (module->fd != -1)
			close(module->fd);
		free(module->path);
		for (struct drgn_elf_file_dwarf_table_iterator it =
		     drgn_elf_file_dwarf_table_first(&module->split_dwarf_files);
		     it.entry;
		     it = drgn_elf_file_dwarf_table_next(it))
			drgn_elf_file_destroy(*it.entry);
		drgn_elf_file_dwarf_table_deinit(&module->split_dwarf_files);
		if (module->debug_file != module->loaded_file)
			drgn_elf_file_destroy(module->debug_file);
		drgn_elf_file_destroy(module->loaded_file);
		free(module->name);
		free(module);
	}
}

static void drgn_module_finish_indexing(struct drgn_debug_info *dbinfo,
					struct drgn_module *module)
{
	module->state = DRGN_DEBUG_INFO_MODULE_INDEXED;
	if (module->name) {
		int ret = c_string_set_insert(&dbinfo->module_names,
					      (const char **)&module->name,
					      NULL);
		/* drgn_debug_info_update_index() should've reserved enough. */
		assert(ret != -1);
	}
}

/*
 * Wrapper around dwfl_report_end() that works around a libdwfl bug which causes
 * it to close stdin when it frees some modules that were reported by
 * dwfl_core_file_report(). This was fixed in elfutils 0.177 by commit
 * d37f6ea7e3e5 ("libdwfl: Fix fd leak/closing wrong fd after
 * dwfl_core_file_report()"), but we support older versions.
 */
static int my_dwfl_report_end(struct drgn_debug_info *dbinfo,
			      int (*removed)(Dwfl_Module *, void *,
					     const char *, Dwarf_Addr, void *),
			      void *arg)
{
	int fd = -1;
	if ((dbinfo->prog->flags
	     & (DRGN_PROGRAM_IS_LINUX_KERNEL | DRGN_PROGRAM_IS_LIVE)) == 0)
		fd = dup(0);
	int ret = dwfl_report_end(dbinfo->dwfl, removed, arg);
	if (fd != -1) {
		dup2(fd, 0);
		close(fd);
	}
	return ret;
}

struct drgn_dwfl_module_removed_arg {
	struct drgn_debug_info *dbinfo;
	bool finish_indexing;
	bool free_all;
};

static int drgn_dwfl_module_removed(Dwfl_Module *dwfl_module, void *userdatap,
				    const char *name, Dwarf_Addr base,
				    void *_arg)
{
	struct drgn_dwfl_module_removed_arg *arg = _arg;
	/*
	 * userdatap is actually a void ** like for the other libdwfl callbacks,
	 * but dwfl_report_end() has the wrong signature for the removed
	 * callback.
	 */
	struct drgn_module *module = *(void **)userdatap;
	if (arg->finish_indexing && module &&
	    module->state == DRGN_DEBUG_INFO_MODULE_INDEXING)
		drgn_module_finish_indexing(arg->dbinfo, module);
	if (arg->free_all || !module ||
	    module->state != DRGN_DEBUG_INFO_MODULE_INDEXED) {
		drgn_module_destroy(module);
	} else {
		/*
		 * The module was already indexed. Report it again so libdwfl
		 * doesn't remove it.
		 */
		Dwarf_Addr end;
		dwfl_module_info(dwfl_module, NULL, NULL, &end, NULL, NULL,
				 NULL, NULL);
		dwfl_report_module(arg->dbinfo->dwfl, name, base, end);
	}
	return DWARF_CB_OK;
}

static void drgn_debug_info_free_modules(struct drgn_debug_info *dbinfo,
					 bool finish_indexing, bool free_all)
{
	for (struct drgn_module_table_iterator it =
	     drgn_module_table_first(&dbinfo->modules); it.entry; ) {
		struct drgn_module *module = *it.entry;
		struct drgn_module **nextp = it.entry;
		do {
			struct drgn_module *next = module->next;
			if (finish_indexing &&
			    module->state == DRGN_DEBUG_INFO_MODULE_INDEXING)
				drgn_module_finish_indexing(dbinfo, module);
			if (free_all ||
			    module->state != DRGN_DEBUG_INFO_MODULE_INDEXED) {
				if (module == *nextp) {
					if (nextp == it.entry && !next) {
						it = drgn_module_table_delete_iterator(&dbinfo->modules,
										       it);
					} else {
						if (!next)
							it = drgn_module_table_next(it);
						*nextp = next;
					}
				}
				void **userdatap;
				dwfl_module_info(module->dwfl_module,
						 &userdatap, NULL, NULL, NULL,
						 NULL, NULL, NULL);
				*userdatap = NULL;
				drgn_module_destroy(module);
			} else {
				if (!next)
					it = drgn_module_table_next(it);
				nextp = &module->next;
			}
			module = next;
		} while (module);
	}

	dwfl_report_begin(dbinfo->dwfl);
	struct drgn_dwfl_module_removed_arg arg = {
		.dbinfo = dbinfo,
		.finish_indexing = finish_indexing,
		.free_all = free_all,
	};
	my_dwfl_report_end(dbinfo, drgn_dwfl_module_removed, &arg);
}

struct drgn_error *
drgn_debug_info_report_error(struct drgn_debug_info_load_state *load,
			     const char *name, const char *message,
			     struct drgn_error *err)
{
	if (err && err->code == DRGN_ERROR_NO_MEMORY) {
		/* Always fail hard if we're out of memory. */
		goto err;
	}
	if (load->num_errors == 0 &&
	    !string_builder_append(&load->errors,
				   "missing some debugging symbols (see https://drgn.readthedocs.io/en/latest/getting_debugging_symbols.html):"))
		goto err;
	if (load->num_errors < load->max_errors) {
		if (!string_builder_line_break(&load->errors))
			goto err;
		if (!string_builder_append(&load->errors, "  "))
			goto err;
		if (name && !string_builder_append(&load->errors, name))
			goto err;
		if (name && (message || err) &&
		    !string_builder_append(&load->errors, " ("))
			goto err;
		if (message && !string_builder_append(&load->errors, message))
			goto err;
		if (message && err &&
		    !string_builder_append(&load->errors, ": "))
			goto err;
		if (err && !string_builder_append_error(&load->errors, err))
			goto err;
		if (name && (message || err) &&
		    !string_builder_appendc(&load->errors, ')'))
			goto err;
	}
	load->num_errors++;
	drgn_error_destroy(err);
	return NULL;

err:
	drgn_error_destroy(err);
	return &drgn_enomem;
}

static struct drgn_error *
drgn_debug_info_report_module(struct drgn_debug_info_load_state *load,
			      const void *build_id, size_t build_id_len,
			      uint64_t start, uint64_t end, const char *name,
			      Dwfl_Module *dwfl_module, const char *path,
			      int fd, Elf *elf, bool *new_ret)
{
	struct drgn_debug_info *dbinfo = load->dbinfo;
	struct drgn_error *err;
	char *path_key = NULL;

	if (new_ret)
		*new_ret = false;

	struct hash_pair hp;
	// Silence -Wmaybe-uninitialized false positive last seen with GCC 12 on
	// i386 and Arm.
	struct drgn_module_table_iterator it = {};
	if (build_id_len) {
		struct drgn_module_key key = {
			.build_id = build_id,
			.build_id_len = build_id_len,
			.start = start,
			.end = end,
		};
		hp = drgn_module_table_hash(&key);
		it = drgn_module_table_search_hashed(&dbinfo->modules, &key,
						     hp);
		if (it.entry &&
		    (*it.entry)->state == DRGN_DEBUG_INFO_MODULE_INDEXED) {
			/* We've already indexed this module. */
			err = NULL;
			goto free;
		}
	}

	if (!dwfl_module) {
		path_key = realpath(path, NULL);
		if (!path_key) {
			path_key = strdup(path);
			if (!path_key) {
				err = &drgn_enomem;
				goto free;
			}
		}

		dwfl_module = dwfl_report_module(dbinfo->dwfl, path_key, start,
						 end);
		if (!dwfl_module) {
			err = drgn_error_libdwfl();
			goto free;
		}
	}

	void **userdatap;
	dwfl_module_info(dwfl_module, &userdatap, NULL, NULL, NULL, NULL, NULL,
			 NULL);
	if (*userdatap) {
		/* We've already reported this file at this offset. */
		err = NULL;
		goto free;
	}
	if (new_ret)
		*new_ret = true;

	struct drgn_module *module = calloc(1, sizeof(*module));
	if (!module) {
		err = &drgn_enomem;
		goto free;
	}
	module->prog = load->dbinfo->prog;
	module->state = DRGN_DEBUG_INFO_MODULE_NEW;
	module->build_id = build_id;
	module->build_id_len = build_id_len;
	module->start = start;
	module->end = end;
	if (name) {
		module->name = strdup(name);
		if (!module->name) {
			err = &drgn_enomem;
			free(module);
			goto free;
		}
	}
	module->dwfl_module = dwfl_module;
	module->path = path_key;
	module->fd = fd;
	module->elf = elf;
	drgn_elf_file_dwarf_table_init(&module->split_dwarf_files);

	/* path_key, fd and elf are owned by the module now. */

	if (!drgn_module_vector_append(&load->new_modules, &module)) {
		drgn_module_destroy(module);
		return &drgn_enomem;
	}
	if (build_id_len) {
		if (it.entry) {
			/*
			 * The first module with this build ID is in
			 * new_modules, so insert it after in the list, not
			 * before.
			 */
			module->next = (*it.entry)->next;
			(*it.entry)->next = module;
		} else if (drgn_module_table_insert_searched(&dbinfo->modules,
							     &module, hp,
							     NULL) < 0) {
			drgn_module_vector_pop(&load->new_modules);
			drgn_module_destroy(module);
			return &drgn_enomem;
		}
	}
	*userdatap = module;
	return NULL;

free:
	elf_end(elf);
	if (fd != -1)
		close(fd);
	free(path_key);
	return err;
}

struct drgn_error *
drgn_debug_info_report_elf(struct drgn_debug_info_load_state *load,
			   const char *path, int fd, Elf *elf, uint64_t start,
			   uint64_t end, const char *name, bool *new_ret)
{

	struct drgn_error *err;
	const void *build_id;
	ssize_t build_id_len = drgn_elf_gnu_build_id(elf, &build_id);
	if (build_id_len < 0) {
		err = drgn_debug_info_report_error(load, path, NULL,
						   drgn_error_libelf());
		elf_end(elf);
		close(fd);
		return err;
	} else if (build_id_len == 0) {
		build_id = NULL;
	}
	return drgn_debug_info_report_module(load, build_id, build_id_len,
					     start, end, name, NULL, path, fd,
					     elf, new_ret);
}

static int drgn_debug_info_report_dwfl_module(Dwfl_Module *dwfl_module,
					      void **userdatap,
					      const char *name, Dwarf_Addr base,
					      void *arg)
{
	struct drgn_debug_info_load_state *load = arg;
	struct drgn_error *err;

	if (*userdatap) {
		/*
		 * This was either reported from drgn_debug_info_report_elf() or
		 * already indexed.
		 */
		return DWARF_CB_OK;
	}

	const unsigned char *build_id;
	GElf_Addr build_id_vaddr;
	int build_id_len = dwfl_module_build_id(dwfl_module, &build_id,
						&build_id_vaddr);
	if (build_id_len < 0) {
		err = drgn_debug_info_report_error(load, name, NULL,
						   drgn_error_libdwfl());
		if (err)
			goto err;
	} else if (build_id_len == 0) {
		build_id = NULL;
	}
	Dwarf_Addr end;
	dwfl_module_info(dwfl_module, NULL, NULL, &end, NULL, NULL, NULL, NULL);
	err = drgn_debug_info_report_module(load, build_id, build_id_len, base,
					    end, NULL, dwfl_module, name, -1,
					    NULL, NULL);
	if (err)
		goto err;
	return DWARF_CB_OK;

err:
	drgn_error_destroy(err);
	return DWARF_CB_ABORT;
}

static struct drgn_error *drgn_get_nt_file(Elf *elf, const char **ret,
					   size_t *len_ret)
{
	size_t phnum;
	if (elf_getphdrnum(elf, &phnum) != 0)
		return drgn_error_libelf();
	for (size_t i = 0; i < phnum; i++) {
		GElf_Phdr phdr_mem, *phdr = gelf_getphdr(elf, i, &phdr_mem);
		if (!phdr)
			return drgn_error_libelf();
		if (phdr->p_type == PT_NOTE) {
			Elf_Data *data = elf_getdata_rawchunk(elf,
							      phdr->p_offset,
							      phdr->p_filesz,
							      note_header_type(phdr->p_align));
			if (!data)
				return drgn_error_libelf();
			GElf_Nhdr nhdr;
			size_t offset = 0, name_offset, desc_offset;
			while (offset < data->d_size &&
			       (offset = gelf_getnote(data, offset, &nhdr,
						      &name_offset,
						      &desc_offset))) {
				const char *name =
					(char *)data->d_buf + name_offset;
				if (nhdr.n_namesz == sizeof("CORE") &&
				    memcmp(name, "CORE", sizeof("CORE")) == 0 &&
				    nhdr.n_type == NT_FILE) {
					*ret = (char *)data->d_buf + desc_offset;
					*len_ret = nhdr.n_descsz;
					return NULL;
				}
			}
		}
	}
	*ret = NULL;
	*len_ret = 0;
	return NULL;
}

struct drgn_mapped_file_segment {
	uint64_t start;
	uint64_t end;
	uint64_t file_offset;
};

DEFINE_VECTOR(drgn_mapped_file_segment_vector, struct drgn_mapped_file_segment);

DEFINE_HASH_MAP(drgn_mapped_files, const char *,
		struct drgn_mapped_file_segment_vector, c_string_key_hash_pair,
		c_string_key_eq);

struct userspace_core_report_state {
	struct drgn_mapped_files files;
	void *phdr_buf;
	size_t phdr_buf_capacity;
	void *segment_buf;
	size_t segment_buf_capacity;
};

static struct drgn_error *parse_nt_file_error(struct binary_buffer *bb,
					      const char *pos,
					      const char *message)
{
	return drgn_error_create(DRGN_ERROR_OTHER, "couldn't parse NT_FILE");
}

static bool
drgn_mapped_file_segments_contiguous(const struct drgn_mapped_file_segment *segment1,
				     const struct drgn_mapped_file_segment *segment2)
{
	if (segment1->end != segment2->start)
		return false;
	uint64_t size = segment1->end - segment1->start;
	return segment1->file_offset + size == segment2->file_offset;
}

static struct drgn_error *
userspace_core_get_mapped_files(struct drgn_debug_info_load_state *load,
				struct userspace_core_report_state *core,
				const char *nt_file, size_t nt_file_len)
{
	struct drgn_error *err;

	GElf_Ehdr ehdr_mem, *ehdr = gelf_getehdr(load->dbinfo->prog->core,
						 &ehdr_mem);
	if (!ehdr)
		return drgn_error_libelf();
	bool is_64_bit = ehdr->e_ident[EI_CLASS] == ELFCLASS64;
	bool little_endian = ehdr->e_ident[EI_DATA] == ELFDATA2LSB;

	struct binary_buffer bb;
	binary_buffer_init(&bb, nt_file, nt_file_len, little_endian,
			   parse_nt_file_error);

	/*
	 * fs/binfmt_elf.c in the Linux kernel source code documents the format
	 * of NT_FILE as:
	 *
	 * long count     -- how many files are mapped
	 * long page_size -- units for file_ofs
	 * array of [COUNT] elements of
	 *   long start
	 *   long end
	 *   long file_ofs
	 * followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
	 */
	uint64_t count, page_size;
	if (is_64_bit) {
		if ((err = binary_buffer_next_u64(&bb, &count)))
			return err;
		if (count > UINT64_MAX / 24)
			return binary_buffer_error(&bb, "count is too large");
		if ((err = binary_buffer_next_u64(&bb, &page_size)) ||
		    (err = binary_buffer_skip(&bb, count * 24)))
			return err;
	} else {
		if ((err = binary_buffer_next_u32_into_u64(&bb, &count)))
			return err;
		if (count > UINT64_MAX / 12)
			return binary_buffer_error(&bb, "count is too large");
		if ((err = binary_buffer_next_u32_into_u64(&bb, &page_size)) ||
		    (err = binary_buffer_skip(&bb, count * 12)))
			return err;
	}

	for (uint64_t i = 0; i < count; i++) {
		struct drgn_mapped_file_segment segment;
		if (is_64_bit) {
			memcpy(&segment, nt_file + 16 + i * 24, 24);
			if (bb.bswap) {
				segment.start = bswap_64(segment.start);
				segment.end = bswap_64(segment.end);
				segment.file_offset = bswap_64(segment.file_offset);
			}
		} else {
			struct {
				uint32_t start;
				uint32_t end;
				uint32_t file_offset;
			} segment32;
			memcpy(&segment32, nt_file + 8 + i * 12, 12);
			if (bb.bswap) {
				segment.start = bswap_32(segment32.start);
				segment.end = bswap_32(segment32.end);
				segment.file_offset = bswap_32(segment32.file_offset);
			} else {
				segment.start = segment32.start;
				segment.end = segment32.end;
				segment.file_offset = segment32.file_offset;
			}
		}
		segment.file_offset *= page_size;

		struct drgn_mapped_files_entry entry = {
			.key = bb.pos,
		};
		if ((err = binary_buffer_skip_string(&bb)))
			return err;
		struct drgn_mapped_files_iterator it;
		int r = drgn_mapped_files_insert(&core->files, &entry, &it);
		if (r < 0)
			return &drgn_enomem;
		if (r == 1)
			drgn_mapped_file_segment_vector_init(&it.entry->value);

		/*
		 * The Linux kernel creates separate entries for contiguous
		 * mappings with different memory protections even though the
		 * protection is not included in NT_FILE. Merge them if we can.
		 */
		if (!drgn_mapped_file_segment_vector_empty(&it.entry->value)
		    && drgn_mapped_file_segments_contiguous(drgn_mapped_file_segment_vector_last(&it.entry->value),
							    &segment))
			drgn_mapped_file_segment_vector_last(&it.entry->value)->end = segment.end;
		else if (!drgn_mapped_file_segment_vector_append(&it.entry->value,
								 &segment))
			return &drgn_enomem;
	}
	return NULL;
}

static bool build_id_matches(Elf *elf, const void *build_id,
			     size_t build_id_len)
{
	const void *elf_build_id;
	ssize_t elf_build_id_len = drgn_elf_gnu_build_id(elf, &elf_build_id);
	if (elf_build_id_len < 0)
		return false;
	return (elf_build_id_len == build_id_len &&
		memcmp(elf_build_id, build_id, build_id_len) == 0);
}

static struct drgn_error *
userspace_core_elf_address_range(uint16_t e_type, size_t phnum,
				 struct drgn_error *(*get_phdr)(void *, size_t, GElf_Phdr *),
				 void *arg,
				 const struct drgn_mapped_file_segment *segments,
				 size_t num_segments,
				 const struct drgn_mapped_file_segment *ehdr_segment,
				 uint64_t *bias_ret, uint64_t *start_ret,
				 uint64_t *end_ret)
{
	struct drgn_error *err;

	/*
	 * First, find the virtual address of the ELF header so that we can
	 * calculate the bias.
	 */
	uint64_t ehdr_vaddr;
	size_t i;
	for (i = 0; i < phnum; i++) {
		GElf_Phdr phdr;
		err = get_phdr(arg, i, &phdr);
		if (err)
			return err;
		if (phdr.p_type == PT_LOAD) {
			uint64_t align = phdr.p_align ? phdr.p_align : 1;
			if ((phdr.p_offset & -align) == 0) {
				ehdr_vaddr = phdr.p_vaddr & -align;
				break;
			}
		}
	}
	if (i >= phnum) {
		/*
		 * No loadable segments contain the ELF header. This can't be
		 * our file.
		 */
		*bias_ret = 0;
not_loaded:
		*start_ret = *end_ret = 0;
		return NULL;
	}
	*bias_ret = ehdr_segment->start - ehdr_vaddr;
	if (*bias_ret != 0 && e_type == ET_EXEC) {
		/* The executable is not loaded at the correct address. */
		goto not_loaded;
	}

	/*
	 * Now check all of the program headers to (1) get the module address
	 * range and (2) make sure that they are mapped as expected. If we're
	 * lucky, this can detect a file that was mmap'd and not actually loaded
	 * by the kernel or dynamic loader. This could also be the wrong file.
	 */
	const struct drgn_mapped_file_segment *segment = segments;
	const struct drgn_mapped_file_segment *end_segment =
		segments + num_segments;
	uint64_t start = 0, end = 0;
	bool first = true;
	for (i = 0; i < phnum; i++) {
		GElf_Phdr phdr;
		err = get_phdr(arg, i, &phdr);
		if (err)
			return err;
		if (phdr.p_type != PT_LOAD)
			continue;
		uint64_t vaddr = phdr.p_vaddr + *bias_ret;
		if (phdr.p_filesz != 0) {
			/*
			 * Advance to the mapped segment containing the start
			 * address.
			 */
			while (vaddr >= segment->end) {
				if (++segment == end_segment)
					goto not_loaded;
				if (vaddr < segment->start)
					goto not_loaded;
			}
			if (segment->file_offset + (vaddr - segment->start) !=
			    phdr.p_offset) {
				/*
				 * The address in the core dump does not map to
				 * the segment's file offset.
				 */
				goto not_loaded;
			}
			if (phdr.p_filesz > segment->end - vaddr) {
				/* Part of the segment is not mapped. */
				goto not_loaded;
			}
		}
		if (first) {
			uint64_t align = phdr.p_align ? phdr.p_align : 1;
			start = vaddr & -align;
			first = false;
		}
		end = vaddr + phdr.p_memsz;
	}
	if (start >= end)
		goto not_loaded;
	*start_ret = start;
	*end_ret = end;
	return NULL;
}

/* ehdr_buf must be aligned as Elf64_Ehdr. */
static void read_ehdr(const void *ehdr_buf, GElf_Ehdr *ret, bool *is_64_bit_ret,
		      bool *bswap_ret)
{
	*is_64_bit_ret = ((unsigned char *)ehdr_buf)[EI_CLASS] == ELFCLASS64;
	bool little_endian =
		((unsigned char *)ehdr_buf)[EI_DATA] == ELFDATA2LSB;
	*bswap_ret = little_endian != HOST_LITTLE_ENDIAN;
	if (*is_64_bit_ret) {
		const Elf64_Ehdr *ehdr64 = ehdr_buf;
		if (*bswap_ret) {
			memcpy(ret->e_ident, ehdr64->e_ident, EI_NIDENT);
			ret->e_type = bswap_16(ehdr64->e_type);
			ret->e_machine = bswap_16(ehdr64->e_machine);
			ret->e_version = bswap_32(ehdr64->e_version);
			ret->e_entry = bswap_64(ehdr64->e_entry);
			ret->e_phoff = bswap_64(ehdr64->e_phoff);
			ret->e_shoff = bswap_64(ehdr64->e_shoff);
			ret->e_flags = bswap_32(ehdr64->e_flags);
			ret->e_ehsize = bswap_16(ehdr64->e_ehsize);
			ret->e_phentsize = bswap_16(ehdr64->e_phentsize);
			ret->e_phnum = bswap_16(ehdr64->e_phnum);
			ret->e_shentsize = bswap_16(ehdr64->e_shentsize);
			ret->e_shnum = bswap_16(ehdr64->e_shnum);
			ret->e_shstrndx = bswap_16(ehdr64->e_shstrndx);
		} else {
			*ret = *ehdr64;
		}
	} else {
		const Elf32_Ehdr *ehdr32 = ehdr_buf;
		memcpy(ret->e_ident, ehdr32->e_ident, EI_NIDENT);
		if (*bswap_ret) {
			ret->e_type = bswap_16(ehdr32->e_type);
			ret->e_machine = bswap_16(ehdr32->e_machine);
			ret->e_version = bswap_32(ehdr32->e_version);
			ret->e_entry = bswap_32(ehdr32->e_entry);
			ret->e_phoff = bswap_32(ehdr32->e_phoff);
			ret->e_shoff = bswap_32(ehdr32->e_shoff);
			ret->e_flags = bswap_32(ehdr32->e_flags);
			ret->e_ehsize = bswap_16(ehdr32->e_ehsize);
			ret->e_phentsize = bswap_16(ehdr32->e_phentsize);
			ret->e_phnum = bswap_16(ehdr32->e_phnum);
			ret->e_shentsize = bswap_16(ehdr32->e_shentsize);
			ret->e_shnum = bswap_16(ehdr32->e_shnum);
			ret->e_shstrndx = bswap_16(ehdr32->e_shstrndx);
		} else {
			ret->e_type = ehdr32->e_type;
			ret->e_machine = ehdr32->e_machine;
			ret->e_version = ehdr32->e_version;
			ret->e_entry = ehdr32->e_entry;
			ret->e_phoff = ehdr32->e_phoff;
			ret->e_shoff = ehdr32->e_shoff;
			ret->e_flags = ehdr32->e_flags;
			ret->e_ehsize = ehdr32->e_ehsize;
			ret->e_phentsize = ehdr32->e_phentsize;
			ret->e_phnum = ehdr32->e_phnum;
			ret->e_shentsize = ehdr32->e_shentsize;
			ret->e_shnum = ehdr32->e_shnum;
			ret->e_shstrndx = ehdr32->e_shstrndx;
		}
	}
}

/* phdr_buf must be aligned as Elf64_Phdr. */
static void read_phdr(const void *phdr_buf, size_t i, bool is_64_bit,
		      bool bswap, GElf_Phdr *ret)
{
	if (is_64_bit) {
		const Elf64_Phdr *phdr64 = (Elf64_Phdr *)phdr_buf + i;
		if (bswap) {
			ret->p_type = bswap_32(phdr64->p_type);
			ret->p_flags = bswap_32(phdr64->p_flags);
			ret->p_offset = bswap_64(phdr64->p_offset);
			ret->p_vaddr = bswap_64(phdr64->p_vaddr);
			ret->p_paddr = bswap_64(phdr64->p_paddr);
			ret->p_filesz = bswap_64(phdr64->p_filesz);
			ret->p_memsz = bswap_64(phdr64->p_memsz);
			ret->p_align = bswap_64(phdr64->p_align);
		} else {
			*ret = *phdr64;
		}
	} else {
		const Elf32_Phdr *phdr32 = (Elf32_Phdr *)phdr_buf + i;
		if (bswap) {
			ret->p_type = bswap_32(phdr32->p_type);
			ret->p_offset = bswap_32(phdr32->p_offset);
			ret->p_vaddr = bswap_32(phdr32->p_vaddr);
			ret->p_paddr = bswap_32(phdr32->p_paddr);
			ret->p_filesz = bswap_32(phdr32->p_filesz);
			ret->p_memsz = bswap_32(phdr32->p_memsz);
			ret->p_flags = bswap_32(phdr32->p_flags);
			ret->p_align = bswap_32(phdr32->p_align);
		} else {
			ret->p_type = phdr32->p_type;
			ret->p_offset = phdr32->p_offset;
			ret->p_vaddr = phdr32->p_vaddr;
			ret->p_paddr = phdr32->p_paddr;
			ret->p_filesz = phdr32->p_filesz;
			ret->p_memsz = phdr32->p_memsz;
			ret->p_flags = phdr32->p_flags;
			ret->p_align = phdr32->p_align;
		}
	}
}

struct core_get_phdr_arg {
	const void *phdr_buf;
	bool is_64_bit;
	bool bswap;
};

static struct drgn_error *
core_get_phdr(void *arg_, size_t i, GElf_Phdr *ret)
{
	struct core_get_phdr_arg *arg = arg_;
	read_phdr(arg->phdr_buf, i, arg->is_64_bit, arg->bswap, ret);
	return NULL;
}

struct userspace_core_identified_file {
	const void *build_id;
	size_t build_id_len;
	uint64_t start, end;
	bool ignore;
	bool have_address_range;
};

static struct drgn_error *
userspace_core_identify_file(struct drgn_program *prog,
			     struct userspace_core_report_state *core,
			     const struct drgn_mapped_file_segment *segments,
			     size_t num_segments,
			     const struct drgn_mapped_file_segment *ehdr_segment,
			     struct userspace_core_identified_file *ret)
{
	struct drgn_error *err;

	Elf64_Ehdr ehdr_buf;
	err = drgn_program_read_memory(prog, &ehdr_buf, ehdr_segment->start,
				       sizeof(ehdr_buf), false);
	if (err) {
		if (err->code == DRGN_ERROR_FAULT) {
			drgn_error_destroy(err);
			err = NULL;
		}
		return err;
	}
	if (memcmp(&ehdr_buf, ELFMAG, SELFMAG) != 0) {
		ret->ignore = true;
		return NULL;
	}

	GElf_Ehdr ehdr;
	struct core_get_phdr_arg arg;
	read_ehdr(&ehdr_buf, &ehdr, &arg.is_64_bit, &arg.bswap);
	if (ehdr.e_type == ET_CORE ||
	    ehdr.e_phnum == 0 ||
	    ehdr.e_phentsize !=
	    (arg.is_64_bit ? sizeof(Elf64_Phdr) : sizeof(Elf32_Phdr))) {
		ret->ignore = true;
		return NULL;
	}

	if (ehdr.e_phnum > SIZE_MAX / ehdr.e_phentsize ||
	    !alloc_or_reuse(&core->phdr_buf, &core->phdr_buf_capacity,
			    ehdr.e_phnum * ehdr.e_phentsize))
		return &drgn_enomem;

	/*
	 * Check whether the mapped segment containing the file header also
	 * contains the program headers. This seems to be the case in practice.
	 */
	uint64_t ehdr_segment_file_end =
		(ehdr_segment->file_offset +
		 (ehdr_segment->end - ehdr_segment->start));
	if (ehdr_segment_file_end < ehdr.e_phoff ||
	    ehdr_segment_file_end - ehdr.e_phoff <
	    ehdr.e_phnum * ehdr.e_phentsize)
		return NULL;

	err = drgn_program_read_memory(prog, core->phdr_buf,
				       ehdr_segment->start + ehdr.e_phoff,
				       ehdr.e_phnum * ehdr.e_phentsize, false);
	if (err) {
		if (err->code == DRGN_ERROR_FAULT) {
			drgn_error_destroy(err);
			err = NULL;
		}
		return err;
	}
	arg.phdr_buf = core->phdr_buf;

	/*
	 * In theory, if the program has a huge number of program headers, they
	 * may not all be dumped. However, the largest binary I was able to find
	 * still had all program headers within 1k.
	 *
	 * It'd be more reliable to determine the bias based on the headers that
	 * were saved, use that to read the build ID, use that to find the ELF
	 * file, and then determine the address range directly from the ELF
	 * file. However, we need the address range to report the build ID to
	 * libdwfl, so we do it this way.
	 */
	uint64_t bias;
	err = userspace_core_elf_address_range(ehdr.e_type, ehdr.e_phnum,
					       core_get_phdr, &arg, segments,
					       num_segments, ehdr_segment,
					       &bias, &ret->start, &ret->end);
	if (err)
		return err;
	if (ret->start >= ret->end) {
		ret->ignore = true;
		return NULL;
	}
	ret->have_address_range = true;

	for (uint16_t i = 0; i < ehdr.e_phnum; i++) {
		GElf_Phdr phdr;
		core_get_phdr(&arg, i, &phdr);
		if (phdr.p_type == PT_NOTE) {
			if (phdr.p_filesz > SIZE_MAX ||
			    !alloc_or_reuse(&core->segment_buf,
					    &core->segment_buf_capacity,
					    phdr.p_filesz))
				return &drgn_enomem;
			err = drgn_program_read_memory(prog, core->segment_buf,
						       phdr.p_vaddr + bias,
						       phdr.p_filesz, false);
			if (err) {
				if (err->code == DRGN_ERROR_FAULT) {
					drgn_error_destroy(err);
					continue;
				} else {
					return err;
				}
			}
			ret->build_id_len =
				parse_gnu_build_id_from_notes(core->segment_buf,
							      phdr.p_filesz,
							      phdr.p_align == 8
							      ? 8 : 4,
							      arg.bswap,
							      &ret->build_id);
			if (ret->build_id_len)
				break;
		}
	}
	return NULL;
}

static struct drgn_error *elf_file_get_phdr(void *arg, size_t i,
					    GElf_Phdr *phdr)
{
	if (!gelf_getphdr(arg, i, phdr))
		return drgn_error_libelf();
	return NULL;
}

static struct drgn_error *
userspace_core_maybe_report_file(struct drgn_debug_info_load_state *load,
				 struct userspace_core_report_state *core,
				 const char *path,
				 const struct drgn_mapped_file_segment *segments,
				 size_t num_segments)
{
	struct drgn_error *err;
	struct drgn_program *prog = load->dbinfo->prog;
	for (size_t ehdr_idx = 0; ehdr_idx < num_segments; ehdr_idx++) {
		const struct drgn_mapped_file_segment *ehdr_segment =
			&segments[ehdr_idx];
		/*
		 * There should always be a full page mapped, so even if it's a
		 * 32-bit file, we can read the 64-bit size.
		 */
		if (ehdr_segment->file_offset != 0 ||
		    ehdr_segment->end - ehdr_segment->start < sizeof(Elf64_Ehdr))
			continue;

		/*
		 * This logic is complicated because we're dealing with two data
		 * sources that we can't completely trust: the memory in the
		 * core dump and the file at the path found in the core dump.
		 *
		 * First, we try to identify the mapped file contents in the
		 * core dump. Ideally, this will find a build ID. However, this
		 * can fail for a few reasons:
		 *
		 * 1. The file is not an ELF file.
		 * 2. The ELF file is not an executable or library.
		 * 3. The ELF file does not have a build ID.
		 * 4. The file header was not dumped to the core dump, in which
		 *    case we can't tell whether this is an ELF file. Dumping
		 *    the first page of an executable file has been the default
		 *    behavior since Linux kernel commit 895021552d6f
		 *    ("coredump: default
		 *    CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y") (in v2.6.37), but
		 *    it can be disabled at kernel build time or toggled at
		 *    runtime.
		 * 5. The build ID or the necessary ELF metadata were not dumped
		 *    in the core dump. This can happen if the necessary program
		 *    headers or note segment were not in the first page of the
		 *    file.
		 * 6. The file is mapped but not actually loaded into the
		 *    program (e.g., if the program is a tool like a profiler or
		 *    a debugger that mmaps binaries [like drgn itself!]).
		 *
		 * In cases 1 and 2, we can simply ignore the file. In cases
		 * 3-5, we blindly trust the path in the core dump. We can
		 * sometimes detect case 6 in
		 * userspace_core_elf_address_range().
		 *
		 * There is also the possibility that the program modified or
		 * corrupted the ELF metadata in memory (more likely if the file
		 * was explicitly mmap'd, since the metadata will usually be
		 * read-only if it was loaded properly). We don't deal with that
		 * yet.
		 */
		struct userspace_core_identified_file identity = {};
		err = userspace_core_identify_file(prog, core, segments,
						   num_segments, ehdr_segment,
						   &identity);
		if (err)
			return err;
		if (identity.ignore)
			continue;

#define CLEAR_ELF() do {	\
	elf = NULL;		\
	fd = -1;		\
} while (0)
#define CLOSE_ELF() do {	\
	elf_end(elf);		\
	close(fd);		\
	CLEAR_ELF();		\
} while (0)
		int fd;
		Elf *elf;
		/*
		 * There are a few things that can go wrong here:
		 *
		 * 1. The path no longer exists.
		 * 2. The path refers to a different ELF file than was in the
		 *    core dump.
		 * 3. The path refers to something which isn't a valid ELF file.
		 */
		err = open_elf_file(path, &fd, &elf);
		if (err) {
			drgn_error_destroy(err);
			CLEAR_ELF();
		} else if (identity.build_id_len > 0) {
			if (!build_id_matches(elf, identity.build_id,
					      identity.build_id_len))
				CLOSE_ELF();
		}

		if (elf && !identity.have_address_range) {
			GElf_Ehdr ehdr_mem, *ehdr;
			size_t phnum;
			if ((ehdr = gelf_getehdr(elf, &ehdr_mem)) &&
			    (elf_getphdrnum(elf, &phnum) == 0)) {
				uint64_t bias;
				err = userspace_core_elf_address_range(ehdr->e_type,
								       phnum,
								       elf_file_get_phdr,
								       elf,
								       segments,
								       num_segments,
								       ehdr_segment,
								       &bias,
								       &identity.start,
								       &identity.end);
				if (err || identity.start >= identity.end) {
					drgn_error_destroy(err);
					CLOSE_ELF();
				} else {
					identity.have_address_range = true;
				}
			} else {
				CLOSE_ELF();
			}
		}

		if (elf) {
			err = drgn_debug_info_report_elf(load, path, fd, elf,
							 identity.start,
							 identity.end, NULL,
							 NULL);
			if (err)
				return err;
		} else {
			if (!identity.have_address_range)
				identity.start = identity.end = 0;
			Dwfl_Module *dwfl_module =
				dwfl_report_module(load->dbinfo->dwfl, path,
						   identity.start,
						   identity.end);
			if (!dwfl_module)
				return drgn_error_libdwfl();
			if (identity.build_id_len > 0 &&
			    dwfl_module_report_build_id(dwfl_module,
							identity.build_id,
							identity.build_id_len,
							0))
				return drgn_error_libdwfl();
		}
#undef CLOSE_ELF
#undef CLEAR_ELF
	}
	return NULL;
}

static struct drgn_error *
userspace_core_report_mapped_files(struct drgn_debug_info_load_state *load,
				   struct userspace_core_report_state *core)
{

	struct drgn_error *err;
	for (struct drgn_mapped_files_iterator it =
	     drgn_mapped_files_first(&core->files);
	     it.entry; it = drgn_mapped_files_next(it)) {
		err = userspace_core_maybe_report_file(load, core,
						       it.entry->key,
						       drgn_mapped_file_segment_vector_begin(&it.entry->value),
						       drgn_mapped_file_segment_vector_size(&it.entry->value));
		if (err)
			return err;
	}
	return NULL;
}

static struct drgn_error *
userspace_core_report_debug_info(struct drgn_debug_info_load_state *load,
				 const char *nt_file, size_t nt_file_len)
{
	struct drgn_error *err;

	struct userspace_core_report_state core = {
		.files = HASH_TABLE_INIT,
	};
	err = userspace_core_get_mapped_files(load, &core, nt_file,
					      nt_file_len);
	if (err)
		goto out;
	err = userspace_core_report_mapped_files(load, &core);
out:
	free(core.segment_buf);
	free(core.phdr_buf);
	for (struct drgn_mapped_files_iterator it =
	     drgn_mapped_files_first(&core.files);
	     it.entry; it = drgn_mapped_files_next(it))
		drgn_mapped_file_segment_vector_deinit(&it.entry->value);
	drgn_mapped_files_deinit(&core.files);
	return err;
}

static struct drgn_error *
userspace_report_elf_file(struct drgn_debug_info_load_state *load,
			  const char *path)
{
	struct drgn_error *err;

	int fd;
	Elf *elf;
	err = open_elf_file(path, &fd, &elf);
	if (err)
		goto err;

	GElf_Ehdr ehdr_mem, *ehdr;
	ehdr = gelf_getehdr(elf, &ehdr_mem);
	if (!ehdr) {
		err = drgn_error_libelf();
		goto err_close;
	}
	/*
	 * We haven't implemented a way to get the load address for dynamically
	 * loaded or relocatable files, so for now we report those as unloaded.
	 */
	uint64_t start = 0, end = 0;
	if (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_CORE) {
		err = elf_address_range(elf, 0, &start, &end);
		if (err)
			goto err_close;
	}

	return drgn_debug_info_report_elf(load, path, fd, elf, start, end, NULL,
					  NULL);

err_close:
	elf_end(elf);
	close(fd);
err:
	return drgn_debug_info_report_error(load, path, NULL, err);
}

static struct drgn_error *
userspace_report_debug_info(struct drgn_debug_info_load_state *load)
{
	struct drgn_error *err;

	for (size_t i = 0; i < load->num_paths; i++) {
		err = userspace_report_elf_file(load, load->paths[i]);
		if (err)
			return err;
	}

	if (load->load_default) {
		Dwfl *dwfl = load->dbinfo->dwfl;
		struct drgn_program *prog = load->dbinfo->prog;
		if (prog->flags & DRGN_PROGRAM_IS_LIVE) {
			int ret = dwfl_linux_proc_report(dwfl, prog->pid);
			if (ret == -1) {
				return drgn_error_libdwfl();
			} else if (ret) {
				return drgn_error_create_os("dwfl_linux_proc_report",
							    ret, NULL);
			}
		} else {
			const char *nt_file;
			size_t nt_file_len;
			char *env = getenv("DRGN_USE_LIBDWFL_REPORT");
			if (env && atoi(env)) {
				nt_file = NULL;
				nt_file_len = 0;
			} else {
				err = drgn_get_nt_file(prog->core, &nt_file,
						       &nt_file_len);
				if (err)
					return err;
			}
			if (nt_file) {
				err = userspace_core_report_debug_info(load,
								       nt_file,
								       nt_file_len);
				if (err)
					return err;
			} else if (dwfl_core_file_report(dwfl, prog->core,
							 NULL) == -1) {
				return drgn_error_libdwfl();
			}
		}
	}
	return NULL;
}

static int should_apply_relocation_section(Elf *elf, size_t shstrndx,
					   const GElf_Shdr *shdr)
{
	if (shdr->sh_type != SHT_RELA && shdr->sh_type != SHT_REL)
		return 0;

	const char *scnname = elf_strptr(elf, shstrndx, shdr->sh_name);
	if (!scnname)
		return -1;
	if (shdr->sh_type == SHT_RELA) {
		if (!strstartswith(scnname, ".rela."))
			return 0;
		scnname += sizeof(".rela.") - 1;
	} else {
		if (!strstartswith(scnname, ".rel."))
			return 0;
		scnname += sizeof(".rel.") - 1;
	}
	return (strstartswith(scnname, "debug_") ||
		strstartswith(scnname, "orc_"));
}

static inline struct drgn_error *get_reloc_sym_value(const void *syms,
						     size_t num_syms,
						     const uint64_t *sh_addrs,
						     size_t shdrnum,
						     bool is_64_bit,
						     bool bswap,
						     uint32_t r_sym,
						     uint64_t *ret)
{
	if (r_sym >= num_syms) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "invalid ELF relocation symbol");
	}
	uint16_t st_shndx;
	uint64_t st_value;
	if (is_64_bit) {
		const Elf64_Sym *sym = (Elf64_Sym *)syms + r_sym;
		memcpy(&st_shndx, &sym->st_shndx, sizeof(st_shndx));
		memcpy(&st_value, &sym->st_value, sizeof(st_value));
		if (bswap) {
			st_shndx = bswap_16(st_shndx);
			st_value = bswap_64(st_value);
		}
	} else {
		const Elf32_Sym *sym = (Elf32_Sym *)syms + r_sym;
		memcpy(&st_shndx, &sym->st_shndx, sizeof(st_shndx));
		uint32_t st_value32;
		memcpy(&st_value32, &sym->st_value, sizeof(st_value32));
		if (bswap) {
			st_shndx = bswap_16(st_shndx);
			st_value32 = bswap_32(st_value32);
		}
		st_value = st_value32;
	}
	if (st_shndx >= shdrnum) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "invalid ELF symbol section index");
	}
	*ret = sh_addrs[st_shndx] + st_value;
	return NULL;
}

static struct drgn_error *
apply_elf_relas(const struct drgn_relocating_section *relocating,
		Elf_Data *reloc_data, Elf_Data *symtab_data,
		const uint64_t *sh_addrs, size_t shdrnum,
		const struct drgn_platform *platform)
{
	struct drgn_error *err;

	bool is_64_bit = drgn_platform_is_64_bit(platform);
	bool bswap = drgn_platform_bswap(platform);
	apply_elf_reloc_fn *apply_elf_reloc = platform->arch->apply_elf_reloc;

	const void *relocs = reloc_data->d_buf;
	size_t reloc_size = is_64_bit ? sizeof(Elf64_Rela) : sizeof(Elf32_Rela);
	size_t num_relocs = reloc_data->d_size / reloc_size;

	const void *syms = symtab_data->d_buf;
	size_t sym_size = is_64_bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
	size_t num_syms = symtab_data->d_size / sym_size;

	for (size_t i = 0; i < num_relocs; i++) {
		uint64_t r_offset;
		uint32_t r_sym;
		uint32_t r_type;
		int64_t r_addend;
		if (is_64_bit) {
			const Elf64_Rela *rela = (Elf64_Rela *)relocs + i;
			uint64_t r_info;
			memcpy(&r_offset, &rela->r_offset, sizeof(r_offset));
			memcpy(&r_info, &rela->r_info, sizeof(r_info));
			memcpy(&r_addend, &rela->r_addend, sizeof(r_addend));
			if (bswap) {
				r_offset = bswap_64(r_offset);
				r_info = bswap_64(r_info);
				r_addend = bswap_64(r_addend);
			}
			r_sym = ELF64_R_SYM(r_info);
			r_type = ELF64_R_TYPE(r_info);
		} else {
			const Elf32_Rela *rela32 = (Elf32_Rela *)relocs + i;
			uint32_t r_offset32;
			uint32_t r_info32;
			int32_t r_addend32;
			memcpy(&r_offset32, &rela32->r_offset, sizeof(r_offset32));
			memcpy(&r_info32, &rela32->r_info, sizeof(r_info32));
			memcpy(&r_addend32, &rela32->r_addend, sizeof(r_addend32));
			if (bswap) {
				r_offset32 = bswap_32(r_offset32);
				r_info32 = bswap_32(r_info32);
				r_addend32 = bswap_32(r_addend32);
			}
			r_offset = r_offset32;
			r_sym = ELF32_R_SYM(r_info32);
			r_type = ELF32_R_TYPE(r_info32);
			r_addend = r_addend32;
		}
		uint64_t sym_value;
		err = get_reloc_sym_value(syms, num_syms, sh_addrs, shdrnum,
					  is_64_bit, bswap, r_sym, &sym_value);
		if (err)
			return err;

		err = apply_elf_reloc(relocating, r_offset, r_type, &r_addend,
				      sym_value);
		if (err)
			return err;
	}
	return NULL;
}

static struct drgn_error *
apply_elf_rels(const struct drgn_relocating_section *relocating,
	       Elf_Data *reloc_data, Elf_Data *symtab_data,
	       const uint64_t *sh_addrs, size_t shdrnum,
	       const struct drgn_platform *platform)
{
	struct drgn_error *err;

	bool is_64_bit = drgn_platform_is_64_bit(platform);
	bool bswap = drgn_platform_bswap(platform);
	apply_elf_reloc_fn *apply_elf_reloc = platform->arch->apply_elf_reloc;

	const void *relocs = reloc_data->d_buf;
	size_t reloc_size = is_64_bit ? sizeof(Elf64_Rel) : sizeof(Elf32_Rel);
	size_t num_relocs = reloc_data->d_size / reloc_size;

	const void *syms = symtab_data->d_buf;
	size_t sym_size = is_64_bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
	size_t num_syms = symtab_data->d_size / sym_size;

	for (size_t i = 0; i < num_relocs; i++) {
		uint64_t r_offset;
		uint32_t r_sym;
		uint32_t r_type;
		if (is_64_bit) {
			const Elf64_Rel *rel = (Elf64_Rel *)relocs + i;
			uint64_t r_info;
			memcpy(&r_offset, &rel->r_offset, sizeof(r_offset));
			memcpy(&r_info, &rel->r_info, sizeof(r_info));
			if (bswap) {
				r_offset = bswap_64(r_offset);
				r_info = bswap_64(r_info);
			}
			r_sym = ELF64_R_SYM(r_info);
			r_type = ELF64_R_TYPE(r_info);
		} else {
			const Elf32_Rel *rel32 = (Elf32_Rel *)relocs + i;
			uint32_t r_offset32;
			uint32_t r_info32;
			memcpy(&r_offset32, &rel32->r_offset, sizeof(r_offset32));
			memcpy(&r_info32, &rel32->r_info, sizeof(r_info32));
			if (bswap) {
				r_offset32 = bswap_32(r_offset32);
				r_info32 = bswap_32(r_info32);
			}
			r_offset = r_offset32;
			r_sym = ELF32_R_SYM(r_info32);
			r_type = ELF32_R_TYPE(r_info32);
		}
		uint64_t sym_value;
		err = get_reloc_sym_value(syms, num_syms, sh_addrs, shdrnum,
					  is_64_bit, bswap, r_sym, &sym_value);
		if (err)
			return err;

		err = apply_elf_reloc(relocating, r_offset, r_type, NULL,
				      sym_value);
		if (err)
			return err;
	}
	return NULL;
}

/*
 * Before the debugging information in a relocatable ELF file (e.g., Linux
 * kernel module) can be used, it must have ELF relocations applied. This is
 * usually done by libdwfl. However, libdwfl is relatively slow at it. This is a
 * much faster implementation.
 */
static struct drgn_error *relocate_elf_file(Elf *elf)
{
	struct drgn_error *err;

	GElf_Ehdr ehdr_mem, *ehdr;
	ehdr = gelf_getehdr(elf, &ehdr_mem);
	if (!ehdr)
		return drgn_error_libelf();

	if (ehdr->e_type != ET_REL) {
		/* Not a relocatable file. */
		return NULL;
	}

	struct drgn_platform platform;
	drgn_platform_from_elf(ehdr, &platform);
	if (!platform.arch->apply_elf_reloc) {
		/* Unsupported; fall back to libdwfl. */
		return NULL;
	}

	size_t shdrnum;
	if (elf_getshdrnum(elf, &shdrnum))
		return drgn_error_libelf();
	_cleanup_free_ uint64_t *sh_addrs =
		calloc(shdrnum, sizeof(sh_addrs[0]));
	if (!sh_addrs && shdrnum > 0)
		return &drgn_enomem;

	Elf_Scn *scn = NULL;
	while ((scn = elf_nextscn(elf, scn))) {
		GElf_Shdr *shdr, shdr_mem;
		shdr = gelf_getshdr(scn, &shdr_mem);
		if (!shdr)
			return drgn_error_libelf();
		sh_addrs[elf_ndxscn(scn)] = shdr->sh_addr;
	}

	size_t shstrndx;
	if (elf_getshdrstrndx(elf, &shstrndx))
		return drgn_error_libelf();

	Elf_Scn *reloc_scn = NULL;
	while ((reloc_scn = elf_nextscn(elf, reloc_scn))) {
		GElf_Shdr *reloc_shdr, reloc_shdr_mem;
		reloc_shdr = gelf_getshdr(reloc_scn, &reloc_shdr_mem);
		if (!reloc_shdr)
			return drgn_error_libelf();

		int r = should_apply_relocation_section(elf, shstrndx,
							reloc_shdr);
		if (r < 0)
			return drgn_error_libelf();
		if (r) {
			scn = elf_getscn(elf, reloc_shdr->sh_info);
			if (!scn)
				return drgn_error_libelf();
			GElf_Shdr *shdr, shdr_mem;
			shdr = gelf_getshdr(scn, &shdr_mem);
			if (!shdr)
				return drgn_error_libelf();
			if (shdr->sh_type == SHT_NOBITS)
				continue;

			Elf_Scn *symtab_scn = elf_getscn(elf,
							 reloc_shdr->sh_link);
			if (!symtab_scn)
				return drgn_error_libelf();

			Elf_Data *data, *reloc_data, *symtab_data;
			if ((err = read_elf_section(scn, &data)) ||
			    (err = read_elf_section(reloc_scn, &reloc_data)) ||
			    (err = read_elf_section(symtab_scn, &symtab_data)))
				return err;

			struct drgn_relocating_section relocating = {
				.buf = data->d_buf,
				.buf_size = data->d_size,
				.addr = sh_addrs[elf_ndxscn(scn)],
				.bswap = drgn_platform_bswap(&platform),
			};

			if (reloc_shdr->sh_type == SHT_RELA) {
				err = apply_elf_relas(&relocating, reloc_data,
						      symtab_data, sh_addrs,
						      shdrnum, &platform);
			} else {
				err = apply_elf_rels(&relocating, reloc_data,
						     symtab_data, sh_addrs,
						     shdrnum, &platform);
			}
			if (err)
				return err;

			/*
			 * Mark the relocation section as empty so that libdwfl
			 * doesn't try to apply it again.
			 */
			reloc_shdr->sh_size = 0;
			if (!gelf_update_shdr(reloc_scn, reloc_shdr))
				return drgn_error_libelf();
			reloc_data->d_size = 0;
		}
	}
	return NULL;
}

static struct drgn_error *
drgn_module_find_files(struct drgn_debug_info_load_state *load,
		       struct drgn_module *module)
{
	struct drgn_error *err;

	if (module->elf) {
		err = relocate_elf_file(module->elf);
		if (err)
			return err;
	}

	GElf_Addr loaded_file_bias;
	Elf *loaded_elf = NULL;
	Dwarf_Addr debug_file_bias;
	Dwarf *dwarf;
	err = NULL;
	#pragma omp critical(drgn_module_find_files)
	{
		// We don't need the loaded file for the Linux kernel, and we
		// always report the debug file as the main file to libdwfl.
		if (!(load->dbinfo->prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)) {
			loaded_elf = dwfl_module_getelf(module->dwfl_module,
							&loaded_file_bias);
			if (!loaded_elf)
				err = drgn_error_libdwfl();
		}
		if (!err) {
			dwarf = dwfl_module_getdwarf(module->dwfl_module,
						     &debug_file_bias);
			if (!dwarf)
				err = drgn_error_libdwfl();
		}
	}
	if (err)
		return err;

	const char *loaded_file_path;
	const char *debug_file_path;
	dwfl_module_info(module->dwfl_module, NULL, NULL, NULL, NULL, NULL,
			 &loaded_file_path, &debug_file_path);
	// If the loaded file also has debugging information, debug_file_path is
	// NULL. (debug_file_path is also NULL if libdwfl got the debug file
	// from debuginfod, so this isn't 100% correct, but it'll at least
	// identify the module.)
	if (!debug_file_path)
		debug_file_path = loaded_file_path;

	module->debug_file_bias = debug_file_bias;
	err = drgn_elf_file_create(module, debug_file_path, dwarf_getelf(dwarf),
				   &module->debug_file);
	if (err) {
		module->debug_file = NULL;
		return err;
	}
	module->debug_file->dwarf = dwarf;
	if (!module->debug_file->scns[DRGN_SCN_DEBUG_INFO] ||
	    !module->debug_file->scns[DRGN_SCN_DEBUG_ABBREV]) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "missing debugging information sections");
	}

	Dwarf *altdwarf = dwarf_getalt(dwarf);
	if (altdwarf) {
		Elf *altelf = dwarf_getelf(altdwarf);
		if (!altelf)
			return drgn_error_libdw();
		size_t shstrndx;
		if (elf_getshdrstrndx(altelf, &shstrndx))
			return drgn_error_libelf();

		Elf_Scn *scn = NULL;
		while ((scn = elf_nextscn(altelf, scn))) {
			GElf_Shdr shdr_mem;
			GElf_Shdr *shdr = gelf_getshdr(scn, &shdr_mem);
			if (!shdr)
				return drgn_error_libelf();

			if (shdr->sh_type != SHT_PROGBITS)
				continue;
			const char *scnname = elf_strptr(altelf, shstrndx,
							 shdr->sh_name);
			if (!scnname)
				return drgn_error_libelf();

			/*
			 * TODO: save more sections and support imported units.
			 */
			if (strcmp(scnname, ".debug_info") == 0 &&
			    !module->debug_file->alt_debug_info_data) {
				err = read_elf_section(scn,
						       &module->debug_file->alt_debug_info_data);
				if (err)
					return err;
			} else if (strcmp(scnname, ".debug_str") == 0 &&
				   !module->debug_file->alt_debug_str_data) {
				err = read_elf_section(scn,
						       &module->debug_file->alt_debug_str_data);
				if (err)
					return err;
			}
		}
	}
	err = drgn_elf_file_precache_sections(module->debug_file);
	if (err)
		return err;

	if (loaded_elf) {
		module->loaded_file_bias = loaded_file_bias;
		if (loaded_elf == module->debug_file->elf) {
			module->loaded_file = module->debug_file;
		} else {
			err = drgn_elf_file_create(module, loaded_file_path,
						   loaded_elf,
						   &module->loaded_file);
			if (err) {
				module->loaded_file = NULL;
				return err;
			}
		}
	}
	return NULL;
}

static struct drgn_error *
drgn_debug_info_read_module(struct drgn_debug_info_load_state *load,
			    struct drgn_dwarf_index_state *index,
			    struct drgn_module *head)
{
	struct drgn_error *err;
	struct drgn_module *module;
	for (module = head; module; module = module->next) {
		err = drgn_module_find_files(load, module);
		if (err) {
			module->err = err;
			continue;
		}
		module->state = DRGN_DEBUG_INFO_MODULE_INDEXING;
		return drgn_dwarf_index_read_file(index, module->debug_file);
	}
	/*
	 * We checked all of the files and didn't find debugging information.
	 * Report why for each one.
	 *
	 * (If we did find debugging information, we discard errors on the
	 * unused files.)
	 */
	err = NULL;
	#pragma omp critical(drgn_debug_info_read_module_error)
	for (module = head; module; module = module->next) {
		const char *name =
			dwfl_module_info(module->dwfl_module, NULL, NULL, NULL,
					 NULL, NULL, NULL, NULL);
		if (module->err) {
			err = drgn_debug_info_report_error(load, name, NULL,
							   module->err);
			module->err = NULL;
		} else {
			err = drgn_debug_info_report_error(load, name,
							   "no debugging information",
							   NULL);
		}
		if (err)
			break;
	}
	return err;
}

static struct drgn_error *
drgn_debug_info_update_index(struct drgn_debug_info_load_state *load)
{
	if (drgn_module_vector_empty(&load->new_modules))
		return NULL;
	struct drgn_debug_info *dbinfo = load->dbinfo;
	if (!c_string_set_reserve(&dbinfo->module_names,
				  c_string_set_size(&dbinfo->module_names)
				  + drgn_module_vector_size(&load->new_modules)))
		return &drgn_enomem;

	struct drgn_dwarf_index_state index;
	if (!drgn_dwarf_index_state_init(&index, dbinfo))
		return &drgn_enomem;
	struct drgn_error *err = NULL;
	#pragma omp parallel for schedule(dynamic) num_threads(drgn_num_threads)
	for (size_t i = 0; i < drgn_module_vector_size(&load->new_modules); i++) {
		if (err)
			continue;
		struct drgn_module *module =
			*drgn_module_vector_at(&load->new_modules, i);
		struct drgn_error *module_err =
			drgn_debug_info_read_module(load, &index, module);
		if (module_err) {
			#pragma omp critical(drgn_debug_info_update_index_error)
			if (err)
				drgn_error_destroy(module_err);
			else
				err = module_err;
		}
	}
	if (!err) {
		drgn_debug_info_free_modules(dbinfo, true, false);
		err = drgn_dwarf_info_update_index(&index);
	}
	drgn_dwarf_index_state_deinit(&index);
	return err;
}

struct drgn_error *
drgn_debug_info_report_flush(struct drgn_debug_info_load_state *load)
{
	struct drgn_debug_info *dbinfo = load->dbinfo;
	my_dwfl_report_end(dbinfo, NULL, NULL);
	struct drgn_error *err = drgn_debug_info_update_index(load);
	dwfl_report_begin_add(dbinfo->dwfl);
	if (err)
		return err;
	drgn_module_vector_clear(&load->new_modules);
	return NULL;
}

static struct drgn_error *
drgn_debug_info_report_finalize_errors(struct drgn_debug_info_load_state *load)
{
	if (load->num_errors > load->max_errors &&
	    (!string_builder_line_break(&load->errors) ||
	     !string_builder_appendf(&load->errors, "... %u more",
				     load->num_errors - load->max_errors))) {
		string_builder_deinit(&load->errors);
		return &drgn_enomem;
	}
	if (load->num_errors) {
		return drgn_error_from_string_builder(DRGN_ERROR_MISSING_DEBUG_INFO,
						      &load->errors);
	} else {
		return NULL;
	}
}

struct drgn_error *drgn_debug_info_load(struct drgn_debug_info *dbinfo,
					const char **paths, size_t n,
					bool load_default, bool load_main)
{
	struct drgn_program *prog = dbinfo->prog;
	struct drgn_error *err;

	if (load_default)
		load_main = true;

	const char *max_errors = getenv("DRGN_MAX_DEBUG_INFO_ERRORS");
	struct drgn_debug_info_load_state load = {
		.dbinfo = dbinfo,
		.paths = paths,
		.num_paths = n,
		.load_default = load_default,
		.load_main = load_main,
		.new_modules = VECTOR_INIT,
		.errors = STRING_BUILDER_INIT,
		.max_errors = max_errors ? atoi(max_errors) : 5,
	};
	dwfl_report_begin_add(dbinfo->dwfl);
	if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL)
		err = linux_kernel_report_debug_info(&load);
	else
		err = userspace_report_debug_info(&load);
	my_dwfl_report_end(dbinfo, NULL, NULL);
	if (err)
		goto err;

	/*
	 * userspace_report_debug_info() reports the main debugging information
	 * directly with libdwfl, so we need to report it to dbinfo.
	 */
	if (!(prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) && load_main &&
	    dwfl_getmodules(dbinfo->dwfl, drgn_debug_info_report_dwfl_module,
			    &load, 0)) {
		err = &drgn_enomem;
		goto err;
	}

	err = drgn_debug_info_update_index(&load);
	if (err)
		goto err;

	/*
	 * TODO: for core dumps, we need to add memory reader segments for
	 * read-only segments of the loaded binaries since those aren't saved in
	 * the core dump.
	 */

	err = drgn_debug_info_report_finalize_errors(&load);
out:
	drgn_module_vector_deinit(&load.new_modules);
	return err;

err:
	drgn_debug_info_free_modules(dbinfo, false, false);
	string_builder_deinit(&load.errors);
	goto out;
}

struct elf_symbols_search_arg {
	const char *name;
	uint64_t address;
	enum drgn_find_symbol_flags flags;
	struct drgn_error *err;
	struct drgn_symbol_result_builder *builder;
};

static bool elf_symbol_match(struct elf_symbols_search_arg *arg, GElf_Addr addr,
			 const GElf_Sym *sym, const char *name)
{
	if ((arg->flags & DRGN_FIND_SYMBOL_NAME) && strcmp(name, arg->name) != 0)
		return false;
	if ((arg->flags & DRGN_FIND_SYMBOL_ADDR) &&
	    (arg->address < addr || arg->address >= addr + sym->st_size))
		return false;
	return true;
}

static bool elf_symbol_store_match(struct elf_symbols_search_arg *arg,
				   GElf_Sym *elf_sym, GElf_Addr addr,
				   const char *name)
{
	struct drgn_symbol *sym;
	if (arg->flags == (DRGN_FIND_SYMBOL_ONE | DRGN_FIND_SYMBOL_NAME)) {
		int binding = GELF_ST_BIND(elf_sym->st_info);
		/*
		 * The order of precedence is
		 * GLOBAL = UNIQUE > WEAK > LOCAL = everything else
		 *
		 * If we found a global or unique symbol, return it
		 * immediately. If we found a weak symbol, then save it,
		 * which may overwrite a previously found weak or local
		 * symbol. Otherwise, save the symbol only if we haven't
		 * found another symbol.
		 */
		if (binding != STB_GLOBAL
		    && binding != STB_GNU_UNIQUE
		    && binding != STB_WEAK
		    && drgn_symbol_result_builder_count(arg->builder) > 0)
			return false;
		sym = malloc(sizeof(*sym));
		if (!sym) {
			arg->err = &drgn_enomem;
			return true;
		}
		drgn_symbol_from_elf(name, addr, elf_sym, sym);
		if (!drgn_symbol_result_builder_add(arg->builder, sym)) {
			arg->err = &drgn_enomem;
			drgn_symbol_destroy(sym);
		}

		/* Abort on error, or short-circuit if we found a global or
		 * unique symbol */
		return (arg->err || sym->binding == DRGN_SYMBOL_BINDING_GLOBAL
			|| sym->binding == DRGN_SYMBOL_BINDING_UNIQUE);
	} else {
		sym = malloc(sizeof(*sym));
		if (!sym) {
			arg->err = &drgn_enomem;
			return true;
		}
		drgn_symbol_from_elf(name, addr, elf_sym, sym);
		if (!drgn_symbol_result_builder_add(arg->builder, sym)) {
			arg->err = &drgn_enomem;
			drgn_symbol_destroy(sym);
		}
		/* Abort on error, or short-circuit for single lookup */
		return (arg->err || (arg->flags & DRGN_FIND_SYMBOL_ONE));
	}
}

static int elf_symbols_search_cb(Dwfl_Module *dwfl_module, void **userdatap,
			     const char *module_name, Dwarf_Addr base,
			     void *cb_arg)
{
	struct elf_symbols_search_arg *arg = cb_arg;

	int symtab_len = dwfl_module_getsymtab(dwfl_module);
	if (symtab_len == -1)
		return DWARF_CB_OK;

	/* Ignore the zeroth null symbol */
	for (int i = 1; i < symtab_len; i++) {
		GElf_Sym elf_sym;
		GElf_Addr elf_addr;
		const char *name = dwfl_module_getsym_info(dwfl_module, i,
							   &elf_sym, &elf_addr,
							   NULL, NULL, NULL);
		if (!name || !elf_symbol_match(arg, elf_addr, &elf_sym, name))
			continue;
		if (elf_symbol_store_match(arg, &elf_sym, elf_addr, name))
			return DWARF_CB_ABORT;
	}
	return DWARF_CB_OK;
}

static struct drgn_error *
elf_symbols_search(const char *name, uint64_t addr, enum drgn_find_symbol_flags flags,
		   void *data, struct drgn_symbol_result_builder *builder)
{
	Dwfl_Module *dwfl_module = NULL;
	struct drgn_program *prog = data;
	struct elf_symbols_search_arg arg = {
		.name = name,
		.address = addr,
		.flags = flags,
		.err = NULL,
		.builder = builder,
	};

	if (arg.flags & DRGN_FIND_SYMBOL_ADDR) {
		dwfl_module = dwfl_addrmodule(prog->dbinfo.dwfl, arg.address);
		if (!dwfl_module)
			return NULL;
	}

	if ((arg.flags & (DRGN_FIND_SYMBOL_ADDR | DRGN_FIND_SYMBOL_ONE))
	    == (DRGN_FIND_SYMBOL_ADDR | DRGN_FIND_SYMBOL_ONE)) {
		GElf_Off offset;
		GElf_Sym elf_sym;
		const char *sym_name = dwfl_module_addrinfo(dwfl_module, addr,
							    &offset, &elf_sym,
							    NULL, NULL, NULL);
		if (!sym_name)
			return NULL;
		struct drgn_symbol *sym = malloc(sizeof(*sym));
		if (!sym)
			return &drgn_enomem;
		drgn_symbol_from_elf(sym_name, addr - offset, &elf_sym, sym);
		if (!drgn_symbol_result_builder_add(builder, sym)) {
			arg.err = &drgn_enomem;
			drgn_symbol_destroy(sym);
		}
	} else if (dwfl_module) {
		elf_symbols_search_cb(dwfl_module, NULL, NULL, 0, &arg);
	} else {
		dwfl_getmodules(prog->dbinfo.dwfl, elf_symbols_search_cb, &arg, 0);
	}
	return arg.err;
}

bool drgn_debug_info_is_indexed(struct drgn_debug_info *dbinfo,
				const char *name)
{
	return c_string_set_search(&dbinfo->module_names, &name).entry != NULL;
}

void drgn_debug_info_init(struct drgn_debug_info *dbinfo,
			  struct drgn_program *prog)
{
	dbinfo->prog = prog;
	dbinfo->dwfl = dwfl_begin(&drgn_dwfl_callbacks);
	// This is temporary until we stop using libdwfl, and is extremely
	// unlikely to fail anwyays, so don't bother propagating an error up.
	if (!dbinfo->dwfl)
		abort();
	const struct drgn_type_finder_ops type_finder_ops = {
		.find = drgn_debug_info_find_type,
	};
	drgn_program_register_type_finder_impl(prog, &dbinfo->type_finder,
					       "dwarf", &type_finder_ops,
					       dbinfo, 0);
	const struct drgn_object_finder_ops object_finder_ops = {
		.find = drgn_debug_info_find_object,
	};
	drgn_program_register_object_finder_impl(prog, &dbinfo->object_finder,
						 "dwarf", &object_finder_ops,
						 dbinfo, 0);
	const struct drgn_symbol_finder_ops symbol_finder_ops = {
		.find = elf_symbols_search,
	};
	drgn_program_register_symbol_finder_impl(prog, &dbinfo->symbol_finder,
						 "elf", &symbol_finder_ops,
						 prog, 0);
	drgn_module_table_init(&dbinfo->modules);
	c_string_set_init(&dbinfo->module_names);
	drgn_dwarf_info_init(dbinfo);
}

void drgn_debug_info_deinit(struct drgn_debug_info *dbinfo)
{
	drgn_dwarf_info_deinit(dbinfo);
	c_string_set_deinit(&dbinfo->module_names);
	drgn_debug_info_free_modules(dbinfo, false, true);
	assert(drgn_module_table_empty(&dbinfo->modules));
	drgn_module_table_deinit(&dbinfo->modules);
	dwfl_end(dbinfo->dwfl);
}

struct drgn_elf_file *drgn_module_find_dwarf_file(struct drgn_module *module,
						  Dwarf *dwarf)
{
	if (!module->debug_file)
		return NULL;
	if (dwarf == module->debug_file->dwarf)
		return module->debug_file;
	struct drgn_elf_file_dwarf_table_iterator it =
		drgn_elf_file_dwarf_table_search(&module->split_dwarf_files,
						 &dwarf);
	return it.entry ? *it.entry : NULL;
}

struct drgn_error *
drgn_module_create_split_dwarf_file(struct drgn_module *module,
				    const char *name, Dwarf *dwarf,
				    struct drgn_elf_file **ret)
{
	struct drgn_error *err;
	err = drgn_elf_file_create(module, name, dwarf_getelf(dwarf), ret);
	if (err)
		return err;
	err = drgn_elf_file_precache_sections(*ret);
	if (err) {
		drgn_elf_file_destroy(*ret);
		return err;
	}
	(*ret)->dwarf = dwarf;
	int r = drgn_elf_file_dwarf_table_insert(&module->split_dwarf_files,
						 ret, NULL);
	if (r < 0) {
		drgn_elf_file_destroy(*ret);
		return &drgn_enomem;
	}
	assert(r > 0);
	return NULL;
}

struct drgn_error *
drgn_module_find_cfi(struct drgn_program *prog, struct drgn_module *module,
		     uint64_t pc, struct drgn_elf_file **file_ret,
		     struct drgn_cfi_row **row_ret, bool *interrupted_ret,
		     drgn_register_number *ret_addr_regno_ret)
{
	struct drgn_error *err;

	// If the file's platform doesn't match the program's, we can't use its
	// CFI.
	const bool can_use_loaded_file =
		(module->loaded_file &&
		 drgn_platforms_equal(&module->loaded_file->platform,
				      &prog->platform));
	const bool can_use_debug_file =
		(module->debug_file &&
		 drgn_platforms_equal(&module->debug_file->platform,
				      &prog->platform));

	bool prefer_orc = false;
	if (can_use_debug_file) {
		if (!module->parsed_debug_frame) {
			err = drgn_module_parse_debug_frame(module);
			if (err)
				return err;
			module->parsed_debug_frame = true;
		}
		if (!module->parsed_orc) {
			err = drgn_module_parse_orc(module);
			if (err)
				return err;
			module->parsed_orc = true;
		}

		prefer_orc = drgn_module_should_prefer_orc_cfi(module, pc);

		*file_ret = module->debug_file;
		if (prefer_orc) {
			err = drgn_module_find_orc_cfi(module, pc, row_ret,
						       interrupted_ret,
						       ret_addr_regno_ret);
			if (err != &drgn_not_found)
				return err;
		}
		err = drgn_module_find_dwarf_cfi(module, pc, row_ret,
						 interrupted_ret,
						 ret_addr_regno_ret);
		if (err != &drgn_not_found)
			return err;
	}

	if (can_use_loaded_file) {
		if (!module->parsed_eh_frame) {
			err = drgn_module_parse_eh_frame(module);
			if (err)
				return err;
			module->parsed_eh_frame = true;
		}
		*file_ret = module->loaded_file;
		err = drgn_module_find_eh_cfi(module, pc, row_ret,
					      interrupted_ret,
					      ret_addr_regno_ret);
		if (err != &drgn_not_found)
			return err;
	}

	if (can_use_debug_file && !prefer_orc) {
		err = drgn_module_find_orc_cfi(module, pc, row_ret,
					       interrupted_ret,
					       ret_addr_regno_ret);
		if (err != &drgn_not_found)
			return err;
	}
	return &drgn_not_found;
}

#if !_ELFUTILS_PREREQ(0, 175)
static Elf *dwelf_elf_begin(int fd)
{
	return elf_begin(fd, ELF_C_READ_MMAP_PRIVATE, NULL);
}
#endif

struct drgn_error *open_elf_file(const char *path, int *fd_ret, Elf **elf_ret)
{
	struct drgn_error *err;

	*fd_ret = open(path, O_RDONLY);
	if (*fd_ret == -1)
		return drgn_error_create_os("open", errno, path);
	*elf_ret = dwelf_elf_begin(*fd_ret);
	if (!*elf_ret) {
		err = drgn_error_libelf();
		goto err_fd;
	}
	if (elf_kind(*elf_ret) != ELF_K_ELF) {
		err = drgn_error_create(DRGN_ERROR_OTHER, "not an ELF file");
		goto err_elf;
	}
	return NULL;

err_elf:
	elf_end(*elf_ret);
err_fd:
	close(*fd_ret);
	return err;
}

struct drgn_error *find_elf_file(char **path_ret, int *fd_ret, Elf **elf_ret,
				 const char * const *path_formats, ...)
{
	struct drgn_error *err;
	size_t i;

	for (i = 0; path_formats[i]; i++) {
		va_list ap;
		int ret;
		char *path;
		int fd;
		Elf *elf;

		va_start(ap, path_formats);
		ret = vasprintf(&path, path_formats[i], ap);
		va_end(ap);
		if (ret == -1)
			return &drgn_enomem;
		fd = open(path, O_RDONLY);
		if (fd == -1) {
			free(path);
			continue;
		}
		elf = dwelf_elf_begin(fd);
		if (!elf) {
			close(fd);
			free(path);
			continue;
		}
		if (elf_kind(elf) != ELF_K_ELF) {
			err = drgn_error_format(DRGN_ERROR_OTHER,
						"%s: not an ELF file", path);
			elf_end(elf);
			close(fd);
			free(path);
			return err;
		}
		*path_ret = path;
		*fd_ret = fd;
		*elf_ret = elf;
		return NULL;
	}
	*path_ret = NULL;
	*fd_ret = -1;
	*elf_ret = NULL;
	return NULL;
}

/*
 * Get the start address from the first loadable segment and the end address
 * from the last loadable segment.
 *
 * The ELF specification states that loadable segments are sorted on p_vaddr.
 * However, vmlinux on x86-64 has an out of order segment for .data..percpu, and
 * Arm has a couple for .vector and .stubs. Thankfully, those are placed in the
 * middle by the vmlinux linker script, so we can still rely on the first and
 * last loadable segments.
 */
struct drgn_error *elf_address_range(Elf *elf, uint64_t bias,
				     uint64_t *start_ret, uint64_t *end_ret)
{
	size_t phnum;
	if (elf_getphdrnum(elf, &phnum) != 0)
		return drgn_error_libelf();

	GElf_Phdr phdr_mem, *phdr;
	size_t i;
	for (i = 0; i < phnum; i++) {
		phdr = gelf_getphdr(elf, i, &phdr_mem);
		if (!phdr)
			return drgn_error_libelf();
		if (phdr->p_type == PT_LOAD) {
			uint64_t align = phdr->p_align ? phdr->p_align : 1;
			*start_ret = (phdr->p_vaddr & -align) + bias;
			break;
		}
	}
	if (i >= phnum) {
		/* There were no loadable segments. */
		*start_ret = *end_ret = 0;
		return NULL;
	}

	for (i = phnum; i-- > 0;) {
		phdr = gelf_getphdr(elf, i, &phdr_mem);
		if (!phdr)
			return drgn_error_libelf();
		if (phdr->p_type == PT_LOAD) {
			*end_ret = (phdr->p_vaddr + phdr->p_memsz) + bias;
			if (*start_ret >= *end_ret)
				*start_ret = *end_ret = 0;
			return NULL;
		}
	}
	/* We found a loadable segment earlier, so this shouldn't happen. */
	assert(!"PT_LOAD segment disappeared");
	*end_ret = 0;
	return NULL;
}
