Skip to content

Commit 5ac95e4

Browse files
committed
libdrgn: fix _page_offset() helper and move to object finder
The internal _page_offset() helper gets the value of PAGE_OFFSET, but the fallback when KASLR is disabled has been out of date since Linux v4.20 and never handled 5-level page tables. Additionally, it makes more sense as part of the Linux kernel (formerly vmcoreinfo) object finder so that it's cleanly accessible outside of drgn internals.
1 parent 1dbc718 commit 5ac95e4

File tree

8 files changed

+110
-23
lines changed

8 files changed

+110
-23
lines changed

drgn/helpers/linux/mm.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,6 @@ def _vmemmap(prog):
4444
return Object(prog, "struct page *", value=0xFFFFEA0000000000)
4545

4646

47-
def _page_offset(prog):
48-
try:
49-
# KASAN
50-
return prog["page_offset_base"].value_()
51-
except KeyError:
52-
# x86-64
53-
return 0xFFFF880000000000
54-
55-
5647
def for_each_page(prog):
5748
"""
5849
Iterate over all pages in the system.
@@ -101,7 +92,7 @@ def virt_to_pfn(prog_or_addr, addr=None):
10192
addr = prog_or_addr.value_()
10293
else:
10394
prog = prog_or_addr
104-
return Object(prog, "unsigned long", value=(addr - _page_offset(prog)) >> 12)
95+
return Object(prog, "unsigned long", value=(addr - prog["PAGE_OFFSET"]) >> 12)
10596

10697

10798
def pfn_to_virt(prog_or_pfn, pfn=None):
@@ -117,7 +108,7 @@ def pfn_to_virt(prog_or_pfn, pfn=None):
117108
pfn = prog_or_pfn.value_()
118109
else:
119110
prog = prog_or_pfn
120-
return Object(prog, "void *", value=(pfn << 12) + _page_offset(prog))
111+
return Object(prog, "void *", value=(pfn << 12) + prog["PAGE_OFFSET"])
121112

122113

123114
def page_to_virt(page):

libdrgn/arch_x86_64.c.in

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "internal.h"
66
#include "platform.h"
7+
#include "program.h"
78
%}
89

910
x86-64
@@ -274,11 +275,81 @@ out:
274275
return err;
275276
}
276277

278+
static struct drgn_error *
279+
linux_kernel_get_page_offset_x86_64(struct drgn_program *prog, uint64_t *ret)
280+
{
281+
struct drgn_error *err;
282+
struct drgn_object obj;
283+
bool nonzero;
284+
285+
/*
286+
* If KASLR is enabled, PAGE_OFFSET is easily available via
287+
* page_offset_base.
288+
*/
289+
drgn_object_init(&obj, prog);
290+
err = drgn_program_find_object(prog, "page_offset_base", NULL,
291+
DRGN_FIND_OBJECT_VARIABLE, &obj);
292+
if (!err) {
293+
err = drgn_object_read_unsigned(&obj, ret);
294+
goto out;
295+
}
296+
if (err->code == DRGN_ERROR_LOOKUP)
297+
drgn_error_destroy(err);
298+
else
299+
goto out;
300+
301+
/*
302+
* If not, we determine PAGE_OFFSET based on the kernel page table. On
303+
* x86_64, swapper_pg_dir is a macro defined to init_top_pgt, which
304+
* itself is defined in assembly. They're both available in VMCOREINFO,
305+
* but it's easier to get it from init_mm.pgd.
306+
*/
307+
err = drgn_program_find_object(prog, "init_mm", NULL,
308+
DRGN_FIND_OBJECT_VARIABLE, &obj);
309+
if (err)
310+
goto out;
311+
err = drgn_object_member(&obj, &obj, "pgd");
312+
if (err)
313+
goto out;
314+
315+
/*
316+
* Before Linux kernel commit d52888aa2753 ("x86/mm: Move LDT remap out
317+
* of KASLR region on 5-level paging") (in v4.20), PAGE_OFFSET was pgd
318+
* slot 272. After that, it is pgd slot 273, and slot 272 is empty
319+
* (reserved for Local Descriptor Table mappings for userspace tasks).
320+
*/
321+
err = drgn_object_subscript(&obj, &obj, 272);
322+
if (err)
323+
goto out;
324+
err = drgn_object_member(&obj, &obj, "pgd");
325+
if (err)
326+
goto out;
327+
err = drgn_object_bool(&obj, &nonzero);
328+
if (err)
329+
goto out;
330+
if (nonzero) {
331+
if (prog->vmcoreinfo.pgtable_l5_enabled)
332+
*ret = UINT64_C(0xff10000000000000);
333+
else
334+
*ret = UINT64_C(0xffff880000000000);
335+
} else {
336+
if (prog->vmcoreinfo.pgtable_l5_enabled)
337+
*ret = UINT64_C(0xff11000000000000);
338+
else
339+
*ret = UINT64_C(0xffff888000000000);
340+
}
341+
342+
out:
343+
drgn_object_deinit(&obj);
344+
return err;
345+
}
346+
277347
const struct drgn_architecture_info arch_info_x86_64 = {
278348
ARCHITECTURE_INFO,
279349
.default_flags = (DRGN_PLATFORM_IS_64_BIT |
280350
DRGN_PLATFORM_IS_LITTLE_ENDIAN),
281351
.frame_registers = frame_registers_x86_64,
282352
.num_frame_registers = ARRAY_SIZE(frame_registers_x86_64),
283353
.linux_kernel_set_initial_registers = linux_kernel_set_initial_registers_x86_64,
354+
.linux_kernel_get_page_offset = linux_kernel_get_page_offset_x86_64,
284355
};

libdrgn/kdump.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ struct drgn_error *drgn_program_set_kdump(struct drgn_program *prog)
125125
}
126126

127127
prog->flags |= DRGN_PROGRAM_IS_LINUX_KERNEL;
128-
err = drgn_program_add_object_finder(prog, vmcoreinfo_object_find,
128+
err = drgn_program_add_object_finder(prog, linux_kernel_object_find,
129129
prog);
130130
if (err)
131131
goto err;

libdrgn/linux_kernel.c

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,19 +235,40 @@ struct drgn_error *read_vmcoreinfo_fallback(struct drgn_memory_reader *reader,
235235
return err;
236236
}
237237

238-
struct drgn_error *
239-
vmcoreinfo_object_find(const char *name, size_t name_len, const char *filename,
240-
enum drgn_find_object_flags flags, void *arg,
241-
struct drgn_object *ret)
238+
struct drgn_error *linux_kernel_object_find(const char *name, size_t name_len,
239+
const char *filename,
240+
enum drgn_find_object_flags flags,
241+
void *arg, struct drgn_object *ret)
242242
{
243243
struct drgn_error *err;
244244
struct drgn_program *prog = arg;
245245

246246
if (!filename && (flags & DRGN_FIND_OBJECT_CONSTANT)) {
247247
struct drgn_qualified_type qualified_type = {};
248248

249-
if (name_len == strlen("PAGE_SHIFT") &&
250-
memcmp(name, "PAGE_SHIFT", name_len) == 0) {
249+
if (name_len == strlen("PAGE_OFFSET") &&
250+
memcmp(name, "PAGE_OFFSET", name_len) == 0) {
251+
if (!prog->page_offset) {
252+
if (!prog->has_platform ||
253+
!prog->platform.arch->linux_kernel_get_page_offset)
254+
return &drgn_not_found;
255+
err = prog->platform.arch->linux_kernel_get_page_offset(prog,
256+
&prog->page_offset);
257+
if (err) {
258+
prog->page_offset = 0;
259+
return err;
260+
}
261+
}
262+
263+
err = drgn_type_index_find_primitive(&prog->tindex,
264+
DRGN_C_TYPE_UNSIGNED_LONG,
265+
&qualified_type.type);
266+
if (err)
267+
return err;
268+
return drgn_object_set_unsigned(ret, qualified_type,
269+
prog->page_offset, 0);
270+
} else if (name_len == strlen("PAGE_SHIFT") &&
271+
memcmp(name, "PAGE_SHIFT", name_len) == 0) {
251272
err = drgn_type_index_find_primitive(&prog->tindex,
252273
DRGN_C_TYPE_INT,
253274
&qualified_type.type);

libdrgn/linux_kernel.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ struct drgn_error *read_vmcoreinfo_fallback(struct drgn_memory_reader *reader,
1919
bool have_non_zero_phys_addr,
2020
struct vmcoreinfo *ret);
2121

22-
struct drgn_error *
23-
vmcoreinfo_object_find(const char *name, size_t name_len, const char *filename,
24-
enum drgn_find_object_flags flags, void *arg,
25-
struct drgn_object *ret);
22+
struct drgn_error *linux_kernel_object_find(const char *name, size_t name_len,
23+
const char *filename,
24+
enum drgn_find_object_flags flags,
25+
void *arg, struct drgn_object *ret);
2626

2727
struct drgn_error *
2828
linux_kernel_report_debug_info(struct drgn_program *prog,

libdrgn/platform.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ struct drgn_architecture_info {
3535
size_t num_frame_registers;
3636
struct drgn_error *(*linux_kernel_set_initial_registers)(Dwfl_Thread *,
3737
const struct drgn_object *);
38+
struct drgn_error *(*linux_kernel_get_page_offset)(struct drgn_program *,
39+
uint64_t *);
3840
};
3941

4042
static inline const struct drgn_register *

libdrgn/program.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ drgn_program_set_core_dump(struct drgn_program *prog, const char *path)
412412
}
413413
if (prog->flags & DRGN_PROGRAM_IS_LINUX_KERNEL) {
414414
err = drgn_program_add_object_finder(prog,
415-
vmcoreinfo_object_find,
415+
linux_kernel_object_find,
416416
prog);
417417
if (err)
418418
goto out_segments;

libdrgn/program.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ struct drgn_program {
6969
* Valid iff <tt>flags & DRGN_PROGRAM_IS_LINUX_KERNEL</tt>.
7070
*/
7171
struct vmcoreinfo vmcoreinfo;
72+
/* Cached PAGE_OFFSET. */
73+
uint64_t page_offset;
7274
#ifdef WITH_LIBKDUMPFILE
7375
kdump_ctx_t *kdump_ctx;
7476
#endif

0 commit comments

Comments
 (0)