libdrgn: linux_kernel: get vmemmap generically

AArch64 has changed the location of vmemmap multiple times, and not all
of these can be easily distinguished. Rather than restorting to kernel
version checks, this replaces the vmemmap architecture callback with a
generic approach that gets the vmemmap address directly from the
mem_section table.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
This commit is contained in:
Omar Sandoval 2022-07-12 13:37:12 -07:00
parent a213573b23
commit b28bd9f0a3
5 changed files with 123 additions and 47 deletions

View File

@ -500,46 +500,6 @@ apply_elf_reloc_x86_64(const struct drgn_relocating_section *relocating,
} }
} }
static struct drgn_error *
linux_kernel_get_vmemmap_x86_64(struct drgn_object *ret)
{
struct drgn_error *err;
struct drgn_program *prog = drgn_object_program(ret);
struct drgn_qualified_type qualified_type;
err = drgn_program_find_type(prog, "struct page *", NULL,
&qualified_type);
if (err)
return err;
/* If KASLR is enabled, vmemmap is vmemmap_base. */
struct drgn_object tmp;
drgn_object_init(&tmp, prog);
err = drgn_program_find_object(prog, "vmemmap_base", NULL,
DRGN_FIND_OBJECT_VARIABLE, &tmp);
if (!err) {
err = drgn_object_cast(ret, qualified_type, &tmp);
goto out;
}
if (err->code == DRGN_ERROR_LOOKUP)
drgn_error_destroy(err);
else
goto out;
/* Otherwise, it depends on whether we have 5-level page tables. */
uint64_t value;
if (prog->vmcoreinfo.pgtable_l5_enabled)
value = UINT64_C(0xffd4000000000000);
else
value = UINT64_C(0xffffea0000000000);
err = drgn_object_set_unsigned(ret, qualified_type, value, 0);
out:
drgn_object_deinit(&tmp);
return err;
}
static struct drgn_error * static struct drgn_error *
linux_kernel_live_direct_mapping_fallback_x86_64(struct drgn_program *prog, linux_kernel_live_direct_mapping_fallback_x86_64(struct drgn_program *prog,
uint64_t *address_ret, uint64_t *address_ret,
@ -694,7 +654,6 @@ const struct drgn_architecture_info arch_info_x86_64 = {
.linux_kernel_get_initial_registers = .linux_kernel_get_initial_registers =
linux_kernel_get_initial_registers_x86_64, linux_kernel_get_initial_registers_x86_64,
.apply_elf_reloc = apply_elf_reloc_x86_64, .apply_elf_reloc = apply_elf_reloc_x86_64,
.linux_kernel_get_vmemmap = linux_kernel_get_vmemmap_x86_64,
.linux_kernel_live_direct_mapping_fallback = .linux_kernel_live_direct_mapping_fallback =
linux_kernel_live_direct_mapping_fallback_x86_64, linux_kernel_live_direct_mapping_fallback_x86_64,
.linux_kernel_pgtable_iterator_create = .linux_kernel_pgtable_iterator_create =

View File

@ -57,6 +57,12 @@ struct drgn_error *drgn_program_parse_vmcoreinfo(struct drgn_program *prog,
if (err) if (err)
return err; return err;
break; break;
@case "LENGTH(mem_section)"@
err = parse_vmcoreinfo_u64(value, newline, 0,
&prog->vmcoreinfo.mem_section_length);
if (err)
return err;
break;
@case "NUMBER(pgtable_l5_enabled)"@ @case "NUMBER(pgtable_l5_enabled)"@
{ {
uint64_t tmp; uint64_t tmp;
@ -87,6 +93,7 @@ struct drgn_error *drgn_program_parse_vmcoreinfo(struct drgn_program *prog,
return drgn_error_create(DRGN_ERROR_OTHER, return drgn_error_create(DRGN_ERROR_OTHER,
"VMCOREINFO does not contain valid swapper_pg_dir"); "VMCOREINFO does not contain valid swapper_pg_dir");
} }
/* KERNELOFFSET, pgtable_l5_enabled, and KERNELPACMASK are optional. */ // KERNELOFFSET, LENGTH(mem_section), pgtable_l5_enabled, and
// KERNELPACMASK are optional.
return NULL; return NULL;
} }

View File

@ -213,15 +213,124 @@ linux_kernel_get_uts_release(struct drgn_program *prog, struct drgn_object *ret)
0, 0); 0, 0);
} }
// The vmemmap address can vary depending on architecture, kernel version,
// configuration options, and KASLR. However, we can get it generically from the
// section_mem_map of any valid mem_section.
static struct drgn_error *
linux_kernel_get_vmemmap_address(struct drgn_program *prog, uint64_t *ret)
{
static const uint64_t SECTION_HAS_MEM_MAP = 0x2;
static const uint64_t SECTION_MAP_MASK = ~((UINT64_C(1) << 6) - 1);
struct drgn_error *err;
struct drgn_object mem_section, root, section;
drgn_object_init(&mem_section, prog);
drgn_object_init(&root, prog);
drgn_object_init(&section, prog);
err = drgn_program_find_object(prog, "vmemmap_populate", NULL,
DRGN_FIND_OBJECT_FUNCTION, &mem_section);
if (err) {
if (err->code == DRGN_ERROR_LOOKUP) {
// !CONFIG_SPARSEMEM_VMEMMAP
drgn_error_destroy(err);
err = &drgn_not_found;
}
goto out;
}
err = drgn_program_find_object(prog, "mem_section", NULL,
DRGN_FIND_OBJECT_VARIABLE, &mem_section);
if (err)
goto out;
const uint64_t nr_section_roots = prog->vmcoreinfo.mem_section_length;
uint64_t sections_per_root;
if (drgn_type_kind(mem_section.type) == DRGN_TYPE_ARRAY) {
// If !CONFIG_SPARSEMEM_EXTREME, mem_section is
// struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT],
// and SECTIONS_PER_ROOT is 1.
sections_per_root = 1;
} else {
// If CONFIG_SPARSEMEM_EXTREME, mem_section is
// struct mem_section **mem_section, and SECTIONS_PER_ROOT is
// PAGE_SIZE / sizeof(struct mem_section).
struct drgn_type *mem_section_type = mem_section.type;
for (int i = 0; i < 2; i++) {
if (drgn_type_kind(mem_section_type) != DRGN_TYPE_POINTER) {
unrecognized_mem_section_type:
err = drgn_type_error("mem_section has unrecognized type '%s'",
mem_section.type);
goto out;
}
mem_section_type = drgn_type_type(mem_section_type).type;
}
if (drgn_type_kind(mem_section_type) != DRGN_TYPE_STRUCT)
goto unrecognized_mem_section_type;
uint64_t sizeof_mem_section = drgn_type_size(mem_section_type);
if (sizeof_mem_section == 0)
goto unrecognized_mem_section_type;
sections_per_root =
prog->vmcoreinfo.page_size / sizeof_mem_section;
}
// Find a valid section.
for (uint64_t i = 0; i < nr_section_roots; i++) {
err = drgn_object_subscript(&root, &mem_section, i);
if (err)
goto out;
bool truthy;
err = drgn_object_bool(&root, &truthy);
if (err)
goto out;
if (!truthy)
continue;
for (uint64_t j = 0; j < sections_per_root; j++) {
err = drgn_object_subscript(&section, &root, j);
if (err)
goto out;
err = drgn_object_member(&section, &section,
"section_mem_map");
if (err)
goto out;
uint64_t section_mem_map;
err = drgn_object_read_unsigned(&section,
&section_mem_map);
if (err)
goto out;
if (section_mem_map & SECTION_HAS_MEM_MAP) {
*ret = section_mem_map & SECTION_MAP_MASK;
err = NULL;
goto out;
}
}
}
err = &drgn_not_found;
out:
drgn_object_deinit(&section);
drgn_object_deinit(&root);
drgn_object_deinit(&mem_section);
return err;
}
static struct drgn_error *linux_kernel_get_vmemmap(struct drgn_program *prog, static struct drgn_error *linux_kernel_get_vmemmap(struct drgn_program *prog,
struct drgn_object *ret) struct drgn_object *ret)
{ {
struct drgn_error *err; struct drgn_error *err;
if (prog->vmemmap.kind == DRGN_OBJECT_ABSENT) { if (prog->vmemmap.kind == DRGN_OBJECT_ABSENT) {
if (!prog->has_platform || uint64_t address;
!prog->platform.arch->linux_kernel_get_vmemmap) err = linux_kernel_get_vmemmap_address(prog, &address);
return &drgn_not_found; if (err)
err = prog->platform.arch->linux_kernel_get_vmemmap(&prog->vmemmap); return err;
struct drgn_qualified_type qualified_type;
err = drgn_program_find_type(prog, "struct page *", NULL,
&qualified_type);
if (err)
return err;
err = drgn_object_set_unsigned(&prog->vmemmap, qualified_type,
address, 0);
if (err) if (err)
return err; return err;
} }

View File

@ -159,7 +159,6 @@ struct drgn_architecture_info {
struct drgn_error *(*linux_kernel_get_initial_registers)(const struct drgn_object *, struct drgn_error *(*linux_kernel_get_initial_registers)(const struct drgn_object *,
struct drgn_register_state **); struct drgn_register_state **);
apply_elf_reloc_fn *apply_elf_reloc; apply_elf_reloc_fn *apply_elf_reloc;
struct drgn_error *(*linux_kernel_get_vmemmap)(struct drgn_object *);
struct drgn_error *(*linux_kernel_live_direct_mapping_fallback)(struct drgn_program *, struct drgn_error *(*linux_kernel_live_direct_mapping_fallback)(struct drgn_program *,
uint64_t *, uint64_t *,
uint64_t *); uint64_t *);

View File

@ -161,6 +161,8 @@ struct drgn_program {
uint64_t kaslr_offset; uint64_t kaslr_offset;
/** Kernel page table. */ /** Kernel page table. */
uint64_t swapper_pg_dir; uint64_t swapper_pg_dir;
/** Length of mem_section array (i.e., NR_SECTION_ROOTS). */
uint64_t mem_section_length;
/** Whether 5-level paging was enabled. */ /** Whether 5-level paging was enabled. */
bool pgtable_l5_enabled; bool pgtable_l5_enabled;
/** PAGE_SHIFT of the kernel (derived from PAGE_SIZE). */ /** PAGE_SHIFT of the kernel (derived from PAGE_SIZE). */