mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 09:43:06 +00:00
23574e59d5
Before Linux v4.11, /proc/kcore didn't have valid physical addresses, so it's currently not possible to read from physical memory on old kernels. However, if we can figure out the address of the direct mapping, then we can determine the corresponding physical addresses for the segments and add them.
420 lines
9.1 KiB
C
420 lines
9.1 KiB
C
%{
|
|
// Copyright 2019 - Omar Sandoval
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "internal.h"
|
|
#include "linux_kernel.h"
|
|
#include "platform.h"
|
|
#include "program.h"
|
|
%}
|
|
|
|
x86-64
|
|
%%
|
|
rax
|
|
rdx
|
|
rcx
|
|
rbx
|
|
rsi
|
|
rdi
|
|
rbp
|
|
rsp
|
|
r8
|
|
r9
|
|
r10
|
|
r11
|
|
r12
|
|
r13
|
|
r14
|
|
r15
|
|
# The System V ABI calls this the return address (RA) register, but it's
|
|
# effectively the instruction pointer.
|
|
rip
|
|
xmm0
|
|
xmm1
|
|
xmm2
|
|
xmm3
|
|
xmm4
|
|
xmm5
|
|
xmm6
|
|
xmm7
|
|
xmm8
|
|
xmm9
|
|
xmm10
|
|
xmm11
|
|
xmm12
|
|
xmm13
|
|
xmm14
|
|
xmm15
|
|
st0
|
|
st1
|
|
st2
|
|
st3
|
|
st4
|
|
st5
|
|
st6
|
|
st7
|
|
mm0
|
|
mm1
|
|
mm2
|
|
mm3
|
|
mm4
|
|
mm5
|
|
mm6
|
|
mm7
|
|
rFLAGS
|
|
es
|
|
cs
|
|
ss
|
|
ds
|
|
fs
|
|
gs
|
|
fs.base, 58
|
|
gs.base
|
|
tr, 62
|
|
ldtr
|
|
mxcsr
|
|
fcw
|
|
fsw
|
|
xmm16
|
|
xmm17
|
|
xmm18
|
|
xmm19
|
|
xmm20
|
|
xmm21
|
|
xmm22
|
|
xmm23
|
|
xmm24
|
|
xmm25
|
|
xmm26
|
|
xmm27
|
|
xmm28
|
|
xmm29
|
|
xmm30
|
|
xmm31
|
|
k0, 118
|
|
k1
|
|
k2
|
|
k3
|
|
k4
|
|
k5
|
|
k6
|
|
k7
|
|
bnd0
|
|
bnd1
|
|
bnd2
|
|
bnd3
|
|
%%
|
|
|
|
static const struct drgn_frame_register frame_registers_x86_64[] = {
|
|
{ DRGN_REGISTER_X86_64_rax, 8, 192, "ax", "rax", },
|
|
{ DRGN_REGISTER_X86_64_rdx, 8, 208, "dx", "rdx", },
|
|
{ DRGN_REGISTER_X86_64_rcx, 8, 200, "cx", "rcx", },
|
|
{ DRGN_REGISTER_X86_64_rbx, 8, 152, "bx", "rbx", },
|
|
{ DRGN_REGISTER_X86_64_rsi, 8, 216, "si", "rsi", },
|
|
{ DRGN_REGISTER_X86_64_rdi, 8, 224, "di", "rdi", },
|
|
{ DRGN_REGISTER_X86_64_rbp, 8, 144, "bp", "rbp", },
|
|
{ DRGN_REGISTER_X86_64_rsp, 8, 264, "sp", "rsp", },
|
|
{ DRGN_REGISTER_X86_64_r8, 8, 184, "r8", },
|
|
{ DRGN_REGISTER_X86_64_r9, 8, 176, "r9", },
|
|
{ DRGN_REGISTER_X86_64_r10, 8, 168, "r10", },
|
|
{ DRGN_REGISTER_X86_64_r11, 8, 160, "r11", },
|
|
{ DRGN_REGISTER_X86_64_r12, 8, 136, "r12", },
|
|
{ DRGN_REGISTER_X86_64_r13, 8, 128, "r13", },
|
|
{ DRGN_REGISTER_X86_64_r14, 8, 120, "r14", },
|
|
{ DRGN_REGISTER_X86_64_r15, 8, 112, "r15", },
|
|
{ DRGN_REGISTER_X86_64_rip, 8, 240, "ip", "rip", },
|
|
};
|
|
|
|
static inline struct drgn_error *read_register(struct drgn_object *reg_obj,
|
|
struct drgn_object *frame_obj,
|
|
const char *name,
|
|
Dwarf_Addr *ret)
|
|
{
|
|
struct drgn_error *err;
|
|
uint64_t reg;
|
|
|
|
err = drgn_object_member_dereference(reg_obj, frame_obj, name);
|
|
if (err)
|
|
return err;
|
|
err = drgn_object_read_unsigned(reg_obj, ®);
|
|
if (err)
|
|
return err;
|
|
*ret = reg;
|
|
return NULL;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
set_initial_registers_inactive_task_frame(Dwfl_Thread *thread,
|
|
struct drgn_object *frame_obj)
|
|
{
|
|
struct drgn_error *err;
|
|
struct drgn_object reg_obj;
|
|
Dwarf_Word dwarf_regs[5];
|
|
uint64_t sp;
|
|
|
|
drgn_object_init(®_obj, frame_obj->prog);
|
|
|
|
err = read_register(®_obj, frame_obj, "bx", &dwarf_regs[0]);
|
|
if (err)
|
|
goto out;
|
|
/* rbx is register 3. */
|
|
if (!dwfl_thread_state_registers(thread, 3, 1, dwarf_regs)) {
|
|
err = drgn_error_libdwfl();
|
|
goto out;
|
|
}
|
|
|
|
err = read_register(®_obj, frame_obj, "bp", &dwarf_regs[0]);
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_read_unsigned(frame_obj, &sp);
|
|
if (err)
|
|
goto out;
|
|
/* rbp is register 6. */
|
|
if (!dwfl_thread_state_registers(thread, 6, 1, dwarf_regs)) {
|
|
err = drgn_error_libdwfl();
|
|
goto out;
|
|
}
|
|
|
|
err = read_register(®_obj, frame_obj, "r12", &dwarf_regs[0]);
|
|
if (err)
|
|
goto out;
|
|
err = read_register(®_obj, frame_obj, "r13", &dwarf_regs[1]);
|
|
if (err)
|
|
goto out;
|
|
err = read_register(®_obj, frame_obj, "r14", &dwarf_regs[2]);
|
|
if (err)
|
|
goto out;
|
|
err = read_register(®_obj, frame_obj, "r15", &dwarf_regs[3]);
|
|
if (err)
|
|
goto out;
|
|
err = read_register(®_obj, frame_obj, "ret_addr", &dwarf_regs[4]);
|
|
if (err)
|
|
goto out;
|
|
/* r12-r15 are registers 12-15; register 16 is the return address. */
|
|
if (!dwfl_thread_state_registers(thread, 12, 5, dwarf_regs)) {
|
|
err = drgn_error_libdwfl();
|
|
goto out;
|
|
}
|
|
|
|
err = NULL;
|
|
out:
|
|
drgn_object_deinit(®_obj);
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
linux_kernel_set_initial_registers_x86_64(Dwfl_Thread *thread,
|
|
const struct drgn_object *task_obj)
|
|
{
|
|
struct drgn_error *err;
|
|
struct drgn_program *prog = task_obj->prog;
|
|
struct drgn_object sp_obj;
|
|
struct drgn_qualified_type frame_type;
|
|
uint64_t sp;
|
|
Dwarf_Word dwarf_reg;
|
|
|
|
drgn_object_init(&sp_obj, prog);
|
|
|
|
/*
|
|
*/
|
|
err = drgn_object_member_dereference(&sp_obj, task_obj, "thread");
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_member(&sp_obj, &sp_obj, "sp");
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_read_unsigned(&sp_obj, &sp);
|
|
if (err)
|
|
goto out;
|
|
dwarf_reg = sp;
|
|
/* rsp is register 7. */
|
|
if (!dwfl_thread_state_registers(thread, 7, 1, &dwarf_reg)) {
|
|
err = drgn_error_libdwfl();
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Since Linux kernel commit 0100301bfdf5 ("sched/x86: Rewrite the
|
|
* switch_to() code") (in v4.9), sp points to a struct
|
|
* inactive_task_frame, which we can use to get most registers. Before
|
|
* that, it points to bp.
|
|
*/
|
|
err = drgn_program_find_type(prog, "struct inactive_task_frame *", NULL,
|
|
&frame_type);
|
|
if (!err) {
|
|
err = drgn_object_cast(&sp_obj, frame_type, &sp_obj);
|
|
if (err)
|
|
goto out;
|
|
err = set_initial_registers_inactive_task_frame(thread,
|
|
&sp_obj);
|
|
|
|
} else if (err->code == DRGN_ERROR_LOOKUP) {
|
|
uint64_t bp;
|
|
|
|
drgn_error_destroy(err);
|
|
err = drgn_program_find_type(prog, "unsigned long", NULL,
|
|
&frame_type);
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_set_reference(&sp_obj, frame_type, sp, 0, 0,
|
|
DRGN_PROGRAM_ENDIAN);
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_read_unsigned(&sp_obj, &bp);
|
|
if (err)
|
|
goto out;
|
|
dwarf_reg = bp;
|
|
/* rbp is register 6. */
|
|
if (!dwfl_thread_state_registers(thread, 6, 1, &dwarf_reg)) {
|
|
err = drgn_error_libdwfl();
|
|
goto out;
|
|
}
|
|
err = NULL;
|
|
}
|
|
out:
|
|
drgn_object_deinit(&sp_obj);
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
linux_kernel_get_page_offset_x86_64(struct drgn_program *prog, uint64_t *ret)
|
|
{
|
|
struct drgn_error *err;
|
|
struct drgn_object obj;
|
|
bool nonzero;
|
|
|
|
/*
|
|
* If KASLR is enabled, PAGE_OFFSET is easily available via
|
|
* page_offset_base.
|
|
*/
|
|
drgn_object_init(&obj, prog);
|
|
err = drgn_program_find_object(prog, "page_offset_base", NULL,
|
|
DRGN_FIND_OBJECT_VARIABLE, &obj);
|
|
if (!err) {
|
|
err = drgn_object_read_unsigned(&obj, ret);
|
|
goto out;
|
|
}
|
|
if (err->code == DRGN_ERROR_LOOKUP)
|
|
drgn_error_destroy(err);
|
|
else
|
|
goto out;
|
|
|
|
/*
|
|
* If not, we determine PAGE_OFFSET based on the kernel page table. On
|
|
* x86_64, swapper_pg_dir is a macro defined to init_top_pgt, which
|
|
* itself is defined in assembly. They're both available in VMCOREINFO,
|
|
* but it's easier to get it from init_mm.pgd.
|
|
*/
|
|
err = drgn_program_find_object(prog, "init_mm", NULL,
|
|
DRGN_FIND_OBJECT_VARIABLE, &obj);
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_member(&obj, &obj, "pgd");
|
|
if (err)
|
|
goto out;
|
|
|
|
/*
|
|
* Before Linux kernel commit d52888aa2753 ("x86/mm: Move LDT remap out
|
|
* of KASLR region on 5-level paging") (in v4.20), PAGE_OFFSET was pgd
|
|
* slot 272. After that, it is pgd slot 273, and slot 272 is empty
|
|
* (reserved for Local Descriptor Table mappings for userspace tasks).
|
|
*/
|
|
err = drgn_object_subscript(&obj, &obj, 272);
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_member(&obj, &obj, "pgd");
|
|
if (err)
|
|
goto out;
|
|
err = drgn_object_bool(&obj, &nonzero);
|
|
if (err)
|
|
goto out;
|
|
if (nonzero) {
|
|
if (prog->vmcoreinfo.pgtable_l5_enabled)
|
|
*ret = UINT64_C(0xff10000000000000);
|
|
else
|
|
*ret = UINT64_C(0xffff880000000000);
|
|
} else {
|
|
if (prog->vmcoreinfo.pgtable_l5_enabled)
|
|
*ret = UINT64_C(0xff11000000000000);
|
|
else
|
|
*ret = UINT64_C(0xffff888000000000);
|
|
}
|
|
|
|
out:
|
|
drgn_object_deinit(&obj);
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
linux_kernel_get_vmemmap_x86_64(struct drgn_program *prog, uint64_t *ret)
|
|
{
|
|
|
|
struct drgn_error *err;
|
|
struct drgn_object obj;
|
|
|
|
/* If KASLR is enabled, vmemmap is vmemmap_base. */
|
|
drgn_object_init(&obj, prog);
|
|
err = drgn_program_find_object(prog, "vmemmap_base", NULL,
|
|
DRGN_FIND_OBJECT_VARIABLE, &obj);
|
|
if (!err) {
|
|
err = drgn_object_read_unsigned(&obj, ret);
|
|
goto out;
|
|
}
|
|
if (err->code == DRGN_ERROR_LOOKUP) {
|
|
drgn_error_destroy(err);
|
|
err = NULL;
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
/* Otherwise, it depends on whether we have 5-level page tables. */
|
|
if (prog->vmcoreinfo.pgtable_l5_enabled)
|
|
*ret = UINT64_C(0xffd4000000000000);
|
|
else
|
|
*ret = UINT64_C(0xffffea0000000000);
|
|
|
|
out:
|
|
drgn_object_deinit(&obj);
|
|
return err;
|
|
}
|
|
|
|
static struct drgn_error *
|
|
linux_kernel_live_direct_mapping_fallback_x86_64(struct drgn_program *prog,
|
|
uint64_t *address_ret,
|
|
uint64_t *size_ret)
|
|
{
|
|
struct drgn_error *err;
|
|
unsigned long page_offset_base_address;
|
|
|
|
*size_ret = UINT64_C(1) << 46;
|
|
err = proc_kallsyms_symbol_addr("page_offset_base",
|
|
&page_offset_base_address);
|
|
if (!err) {
|
|
return drgn_program_read_word(prog, page_offset_base_address,
|
|
false, address_ret);
|
|
} else if (err == &drgn_not_found) {
|
|
/*
|
|
* This is only called for pre-4.11 kernels, so we can assume
|
|
* the old location.
|
|
*/
|
|
*address_ret = UINT64_C(0xffff880000000000);
|
|
return NULL;
|
|
} else {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
const struct drgn_architecture_info arch_info_x86_64 = {
|
|
ARCHITECTURE_INFO,
|
|
.default_flags = (DRGN_PLATFORM_IS_64_BIT |
|
|
DRGN_PLATFORM_IS_LITTLE_ENDIAN),
|
|
.frame_registers = frame_registers_x86_64,
|
|
.num_frame_registers = ARRAY_SIZE(frame_registers_x86_64),
|
|
.linux_kernel_set_initial_registers =
|
|
linux_kernel_set_initial_registers_x86_64,
|
|
.linux_kernel_get_page_offset = linux_kernel_get_page_offset_x86_64,
|
|
.linux_kernel_get_vmemmap = linux_kernel_get_vmemmap_x86_64,
|
|
.linux_kernel_live_direct_mapping_fallback =
|
|
linux_kernel_live_direct_mapping_fallback_x86_64,
|
|
};
|