[PATCH] memory: handle kmem_cache per-node pointer layout
by Harshvardhan Jha
Linux commit 5ba6bc27b1f9 ("slab: decouple pointer to barn from
kmem_cache_node") moved the kmem_cache_node pointers from
kmem_cache.node[] to kmem_cache.per_node[].node.
Teach the SLUB traversal code to recognize and handle both layouts.
Without this, crash treats the new layout as non-NUMA and kmem -s fails
while looking for the removed kmem_cache.local_node member.
Signed-off-by: Harshvardhan Jha <harshvardhan.j.jha(a)oracle.com>
---
Testing was performed manually on x86_64 VMs:
- Linux 7.1.0-rc7 with the new per_node layout: upstream crash failed
with the kmem_cache_local_node invalid-offset error, while the patched
crash completed kmem -s and displayed the slab-cache table.
- Linux 6.18.0-rc7 with the old node[1024] layout: the patched crash
completed kmem -s and displayed the slab-cache table.
defs.h | 3 +++
memory.c | 39 ++++++++++++++++++++++++++++++---------
symbols.c | 6 ++++++
3 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/defs.h b/defs.h
index e3027e2..ccd84a0 100644
--- a/defs.h
+++ b/defs.h
@@ -1766,6 +1766,8 @@ struct offset_table { /* stash of commonly-used offsets */
long kmem_cache_name;
long kmem_cache_list;
long kmem_cache_node;
+ long kmem_cache_per_node;
+ long kmem_cache_per_node_node;
long kmem_cache_cpu_slab;
long page_inuse;
/* long page_offset; use "old" page->offset */
@@ -2395,6 +2397,7 @@ struct size_table { /* stash of commonly-used sizes */
long rlimit;
long kmem_cache;
long kmem_cache_node;
+ long kmem_cache_per_node_ptrs;
long upid;
long kmem_cache_cpu;
long cfs_rq;
diff --git a/memory.c b/memory.c
index 3f3aa27..cf26bd4 100644
--- a/memory.c
+++ b/memory.c
@@ -306,6 +306,7 @@ static int get_kmem_cache_list(ulong **);
static int get_kmem_cache_root_list(ulong **);
static int get_kmem_cache_child_list(ulong **, ulong);
static int get_kmem_cache_slub_data(long, struct meminfo *);
+static ulong get_kmem_cache_node_ptr(struct meminfo *, int);
static ulong compound_head(ulong);
static long count_partial(ulong, struct meminfo *, ulong *);
static short count_cpu_partial(struct meminfo *, int);
@@ -869,6 +870,9 @@ vm_init(void)
MEMBER_OFFSET_INIT(kmem_cache_inuse, "kmem_cache", "inuse");
MEMBER_OFFSET_INIT(kmem_cache_align, "kmem_cache", "align");
MEMBER_OFFSET_INIT(kmem_cache_node, "kmem_cache", "node");
+ MEMBER_OFFSET_INIT(kmem_cache_per_node, "kmem_cache", "per_node");
+ MEMBER_OFFSET_INIT(kmem_cache_per_node_node,
+ "kmem_cache_per_node_ptrs", "node");
MEMBER_OFFSET_INIT(kmem_cache_cpu_slab, "kmem_cache", "cpu_slab");
MEMBER_OFFSET_INIT(kmem_cache_list, "kmem_cache", "list");
MEMBER_OFFSET_INIT(kmem_cache_red_left_pad, "kmem_cache", "red_left_pad");
@@ -920,9 +924,16 @@ vm_init(void)
if (INVALID_MEMBER(page_objects))
ANON_MEMBER_OFFSET_INIT(page_objects, "slab", "objects");
}
+ STRUCT_SIZE_INIT(kmem_cache_per_node_ptrs,
+ "kmem_cache_per_node_ptrs");
if (VALID_MEMBER(kmem_cache_node)) {
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache.node", NULL, 0);
vt->flags |= CONFIG_NUMA;
+ } else if (VALID_MEMBER(kmem_cache_per_node) &&
+ VALID_MEMBER(kmem_cache_per_node_node) &&
+ VALID_SIZE(kmem_cache_per_node_ptrs)) {
+ ARRAY_LENGTH_INIT(len, NULL, "kmem_cache.per_node", NULL, 0);
+ vt->flags |= CONFIG_NUMA;
}
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache.cpu_slab", NULL, 0);
@@ -19460,6 +19471,22 @@ count_cpu_partial(struct meminfo *si, int cpu)
return free_objects;
}
+static ulong
+get_kmem_cache_node_ptr(struct meminfo *si, int node)
+{
+ char *node_addr;
+
+ if (VALID_MEMBER(kmem_cache_node))
+ node_addr = si->cache_buf + OFFSET(kmem_cache_node) +
+ (sizeof(void *) * node);
+ else
+ node_addr = si->cache_buf + OFFSET(kmem_cache_per_node) +
+ (SIZE(kmem_cache_per_node_ptrs) * node) +
+ OFFSET(kmem_cache_per_node_node);
+
+ return ULONG(node_addr);
+}
+
/*
* Emulate the total count calculation done by the
* slab_objects() sysfs function in slub.c.
@@ -19537,9 +19564,7 @@ get_kmem_cache_slub_data(long cmd, struct meminfo *si)
for (n = 0; n < vt->numnodes; n++) {
if (vt->flags & CONFIG_NUMA) {
nt = &vt->node_table[n];
- node_ptr = ULONG(si->cache_buf +
- OFFSET(kmem_cache_node) +
- (sizeof(void *) * nt->node_id));
+ node_ptr = get_kmem_cache_node_ptr(si, nt->node_id);
} else
node_ptr = si->cache +
OFFSET(kmem_cache_local_node);
@@ -19709,9 +19734,7 @@ do_kmem_cache_slub(struct meminfo *si)
for (n = 0; n < vt->numnodes; n++) {
if (vt->flags & CONFIG_NUMA) {
nt = &vt->node_table[n];
- node_ptr = ULONG(si->cache_buf +
- OFFSET(kmem_cache_node) +
- (sizeof(void *)* nt->node_id));
+ node_ptr = get_kmem_cache_node_ptr(si, nt->node_id);
} else
node_ptr = si->cache +
OFFSET(kmem_cache_local_node);
@@ -20515,9 +20538,7 @@ slab_to_kmem_cache_node(struct meminfo *si, ulong slab_page)
if (vt->flags & CONFIG_NUMA) {
node = page_to_nid(slab_page);
- node_ptr = ULONG(si->cache_buf +
- OFFSET(kmem_cache_node) +
- (sizeof(void *)*node));
+ node_ptr = get_kmem_cache_node_ptr(si, node);
} else
node_ptr = si->cache + OFFSET(kmem_cache_local_node);
diff --git a/symbols.c b/symbols.c
index a7ccdb1..652fce4 100644
--- a/symbols.c
+++ b/symbols.c
@@ -10922,6 +10922,10 @@ dump_offset_table(char *spec, ulong makestruct)
OFFSET(kmem_cache_red_left_pad));
fprintf(fp, " kmem_cache_node: %ld\n",
OFFSET(kmem_cache_node));
+ fprintf(fp, " kmem_cache_per_node: %ld\n",
+ OFFSET(kmem_cache_per_node));
+ fprintf(fp, " kmem_cache_per_node_node: %ld\n",
+ OFFSET(kmem_cache_per_node_node));
fprintf(fp, " kmem_cache_cpu_slab: %ld\n",
OFFSET(kmem_cache_cpu_slab));
fprintf(fp, " kmem_cache_cpu_partial: %ld\n",
@@ -12042,6 +12046,8 @@ dump_offset_table(char *spec, ulong makestruct)
SIZE(kmem_bufctl_t));
fprintf(fp, " kmem_cache: %ld\n", SIZE(kmem_cache));
fprintf(fp, " kmem_cache_node: %ld\n", SIZE(kmem_cache_node));
+ fprintf(fp, " kmem_cache_per_node_ptrs: %ld\n",
+ SIZE(kmem_cache_per_node_ptrs));
fprintf(fp, " kmem_cache_cpu: %ld\n", SIZE(kmem_cache_cpu));
fprintf(fp, " swap_info_struct: %ld\n",
--
2.52.0
4 days, 13 hours
[PATCH] symbols: use non-debug BFD to retrieve .rodata
by Jiri Slaby
When running crash on openSUSE, get_linux_banner_from_vmlinux() returns
success but fill the target buffer with all zero bytes (`\0`).
This happens because `st->bfd` is initially opened for the main binary
(`vmlinux`), but is later overwritten with the debuginfo file
(`vmlinux.debug`) in `check_gnu_debuglink()`. In separate debug files,
`.rodata` has no content, so is marked only as `ALLOC` without
`SEC_HAS_CONTENTS`.
When `bfd_get_section_contents()` is called on a section without
`SEC_HAS_CONTENTS`, BFD clears the buffer to zeros and returns TRUE. As
a result, `get_linux_banner_from_vmlinux()` thinks it successfully read
the banner, while it actually received zeroed bytes.
Fix this by caching the original main executable's BFD (into
`st->bfd_orig`) before `st->bfd` gets swapped for the debuginfo file.
`get_linux_banner_from_vmlinux()` will then fall back to `st->bfd_orig`
if present, ensuring `.rodata` contents are read from the binary that
actually contains the raw section data.
Fixes #232.
Signed-off-by: Jiri Slaby <jirislaby(a)gmail.com>
Cc: Dave Young <yangrr.2009(a)tsinghua.org.cn>
Cc: <ltao(a)redhat.com>
Cc: <mpilaniy(a)redhat.com>
---
defs.h | 1 +
kernel.c | 5 +++--
symbols.c | 1 +
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/defs.h b/defs.h
index e3027e2b9141..e82af5bd5ed0 100644
--- a/defs.h
+++ b/defs.h
@@ -2920,6 +2920,7 @@ struct symbol_table_data {
#ifdef GDB_5_3
struct _bfd *bfd;
#else
+ struct bfd *bfd_orig;
struct bfd *bfd;
#endif
struct sec *sections;
diff --git a/kernel.c b/kernel.c
index e53038d5d8db..521cdf5664ef 100644
--- a/kernel.c
+++ b/kernel.c
@@ -12145,6 +12145,7 @@ check_vmcoreinfo(void)
static
int get_linux_banner_from_vmlinux(char *buf, size_t size)
{
+ struct bfd *bfd = st->bfd_orig ? : st->bfd;
struct bfd_section *sect;
long offset;
ulong start_rodata;
@@ -12156,7 +12157,7 @@ int get_linux_banner_from_vmlinux(char *buf, size_t size)
else
return FALSE;
- sect = bfd_get_section_by_name(st->bfd, ".rodata");
+ sect = bfd_get_section_by_name(bfd, ".rodata");
if (!sect)
return FALSE;
@@ -12168,7 +12169,7 @@ int get_linux_banner_from_vmlinux(char *buf, size_t size)
*/
offset = symbol_value("linux_banner") - start_rodata;
- if (!bfd_get_section_contents(st->bfd,
+ if (!bfd_get_section_contents(bfd,
sect,
buf,
offset,
diff --git a/symbols.c b/symbols.c
index a7ccdb101033..4b5585d62dba 100644
--- a/symbols.c
+++ b/symbols.c
@@ -444,6 +444,7 @@ check_gnu_debuglink(bfd *bfd)
return FALSE;
reset_bfd:
+ st->bfd_orig = st->bfd;
if ((st->bfd = bfd_openr(pc->debuginfo_file, NULL)) == NULL)
error(FATAL, "cannot open object file: %s\n",
--
2.55.0
4 days, 16 hours
[PATCH V2] Refactor kernel version checking code
by Ruirui Yang
In case linux banner can not be found in vmlinux for some reason.
The fallback checking of "Linux version" failed for kernel 7.x with
below error msg:
WARNING: kernel version inconsistency between vmlinux and dumpfile
crash: incompatible arguments: vmlinux is not SMP -- vmcore is SMP
Jiri's case is OpenSUSE uses separate vmlinux.debug file, details see
https://github.com/crash-utility/crash/issues/232
But update the kernel version number in kernel sanity checking code
does workaround the issue. Let's fix this separately.
A few improvements to the sanity checking including:
- Replace hardcoded version checks (2.x through 6.x) with a unified
kernel_version_str_sanity_check() function
- Extend support to Linux 7.x kernels
- Add proper bounds checking and null pointer validation
- Ensure minor version number is numeric for stricter validation
- Reduce code duplication in verify_namelist() and debug_kernel_version()
Reported-by: Jiri Slaby <jirislaby(a)gmail.com>
Signed-off-by: Dave Young <yangrr.2009(a)tsinghua.org.cn>
---
[V1->V2]: refactore the code; address comments from Mukesh
kernel.c | 39 +++++++++++++++++++++++++++++----------
1 file changed, 29 insertions(+), 10 deletions(-)
Index: crash/kernel.c
===================================================================
--- crash.orig/kernel.c 2026-08-14 11:21:39.972971968 +0800
+++ crash/kernel.c 2026-08-14 11:21:42.371976057 +0800
@@ -29,6 +29,9 @@
#endif
#include "bfd.h"
+#define KERNEL_VERSION_MIN '2'
+#define KERNEL_VERSION_MAX '7' /* latest linux mainline kernel major number */
+
static void do_module_cmd(ulong, char *, ulong, char *, char *);
static void show_module_taint(void);
static char *find_module_objfile(char *, char *, char *);
@@ -104,6 +107,30 @@
static int is_pvops_xen(void);
static int get_linux_banner_from_vmlinux(char *, size_t);
+static bool kernel_version_str_sanity_check(char *buf)
+{
+ int n;
+ char *p;
+
+ if (!buf)
+ return FALSE;
+
+ p = strstr(buf, "Linux version ");
+ if (!p)
+ return FALSE;
+
+ n = strlen(p);
+
+ if (n < 17) /* "Linux version " (14) + "x.y" (3) = 17 */
+ return FALSE;
+
+ if (p[14] >= KERNEL_VERSION_MIN && p[14] <= KERNEL_VERSION_MAX
+ && p[15] == '.' && p[16] >= '0' && p[16] <= '9')
+ return TRUE;
+
+ return FALSE;
+}
+
/*
* popuplate the global kernel table (kt) with kernel version
* information parsed from UTSNAME/OSRELEASE string
@@ -1396,11 +1423,7 @@
found = FALSE;
sprintf(buffer3, "(unknown)");
while (fgets(buffer, (BUFSIZE/2)-1, pipe)) {
- if (!strstr(buffer, "Linux version 2.") &&
- !strstr(buffer, "Linux version 3.") &&
- !strstr(buffer, "Linux version 4.") &&
- !strstr(buffer, "Linux version 5.") &&
- !strstr(buffer, "Linux version 6."))
+ if (!kernel_version_str_sanity_check(buffer))
continue;
if (strstr(buffer, kt->proc_version)) {
@@ -5987,11 +6010,7 @@
argc = 0;
while (fgets(buf, BUFSIZE-1, pipe)) {
- if (!strstr(buf, "Linux version 2.") &&
- !strstr(buf, "Linux version 3.") &&
- !strstr(buf, "Linux version 4.") &&
- !strstr(buf, "Linux version 5.") &&
- !strstr(buf, "Linux version 6."))
+ if (!kernel_version_str_sanity_check(buf))
continue;
argc = parse_line(buf, arglist);
4 days, 16 hours
[PATCH] Fix compound_head() and "bt -F" option on Linux 7.1 and later
by HAGIO KAZUHITO(萩尾 一仁)
From: Kazuhito Hagio <k-hagio-ab(a)nec.com>
Kernel commit d50569612c29 ("mm: rename the 'compound_head' field in the
'struct page' to 'compound_info'") and related patches [1] changed the
field name and its value.
Without the patch, crash prints the following warning during startup,
WARNING: SLUB: cannot determine how compound pages are linked
and "bt -F" option fails with the following error.
crash> bt -F
bt: invalid structure member offset: page_first_page
FILE: memory.c LINE: 20238 FUNCTION: compound_head()
[1] https://lore.kernel.org/all/20260227194302.274384-1-kas@kernel.org/
Signed-off-by: Kazuhito Hagio <k-hagio-ab(a)nec.com>
---
defs.h | 1 +
memory.c | 44 ++++++++++++++++++++++++++++++++++++--------
symbols.c | 1 +
3 files changed, 38 insertions(+), 8 deletions(-)
diff --git a/defs.h b/defs.h
index e3027e2b9141..88b7bbeb2372 100644
--- a/defs.h
+++ b/defs.h
@@ -2295,6 +2295,7 @@ struct offset_table { /* stash of commonly-used offsets */
long page_compound_order;
long folio__folio_order;
long folio__flags_1;
+ long page_compound_info;
};
struct size_table { /* stash of commonly-used sizes */
diff --git a/memory.c b/memory.c
index 3f3aa274ab5a..7102e951f1d5 100644
--- a/memory.c
+++ b/memory.c
@@ -414,6 +414,7 @@ mem_init(void)
#define FOLIO_ORDER_V2 2
#define FOLIO_ORDER_V3 3
static int folio_order_version;
+static int compound_info_has_mask = FALSE;
/*
* Stash a few popular offsets and some basic kernel virtual memory
@@ -547,6 +548,7 @@ vm_init(void)
MEMBER_OFFSET_INIT(page_compound_head, "page", "compound_head");
if (INVALID_MEMBER(page_compound_head))
ANON_MEMBER_OFFSET_INIT(page_compound_head, "page", "compound_head");
+ MEMBER_OFFSET_INIT(page_compound_info, "page", "compound_info");
MEMBER_OFFSET_INIT(page_private, "page", "private");
MEMBER_OFFSET_INIT(page_freelist, "page", "freelist");
MEMBER_OFFSET_INIT(page_page_type, "page", "page_type");
@@ -1352,6 +1354,17 @@ vm_init(void)
} else if (CRASHDEBUG(1))
error(NOTE, "page_hash_table does not exist in this kernel\n");
+ /*
+ * on Linux 7.1 and later, zone.vmemmap_tails is defined only when
+ * CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP is enabled.
+ */
+#define is_power_of_2(n) (n - 1 < (n ^ (n - 1)))
+ if (VALID_MEMBER(page_compound_info) &&
+ is_power_of_2(SIZE(page)) && MEMBER_EXISTS("zone", "vmemmap_tails"))
+ compound_info_has_mask = TRUE;
+ if (CRASHDEBUG(1))
+ error(NOTE, "compound_info_has_mask = %d\n", compound_info_has_mask);
+
kmem_cache_init();
page_flags_init();
@@ -5642,10 +5655,11 @@ PG_slab_flag_init(void)
}
}
- if (VALID_MEMBER(page_compound_head)) {
+ if (VALID_MEMBER(page_compound_head) || VALID_MEMBER(page_compound_info)) {
if (CRASHDEBUG(2))
fprintf(fp,
- "PG_head_tail_mask: (UNUSED): page.compound_head exists!\n");
+ "PG_head_tail_mask: (UNUSED): page.compound_head or "
+ "page.compound_info exists!\n");
} else if (vt->flags & KMALLOC_SLUB) {
/*
* PG_slab and the following are hardwired for
@@ -9858,7 +9872,8 @@ vaddr_to_kmem_cache(ulong vaddr, char *buf, int verbose)
&page_flags, sizeof(ulong), "page.flags",
FAULT_ON_ERROR);
if (!page_slab(page, page_flags)) {
- if (((vt->flags & KMALLOC_SLUB) || VALID_MEMBER(page_compound_head)) ||
+ if (((vt->flags & KMALLOC_SLUB) || VALID_MEMBER(page_compound_head) ||
+ VALID_MEMBER(page_compound_info)) ||
((vt->flags & KMALLOC_COMMON) &&
VALID_MEMBER(page_slab) && VALID_MEMBER(page_first_page))) {
readmem(compound_head(page)+OFFSET(page_flags), KVADDR,
@@ -9873,7 +9888,8 @@ vaddr_to_kmem_cache(ulong vaddr, char *buf, int verbose)
if ((vt->flags & KMALLOC_SLUB) ||
((vt->flags & KMALLOC_COMMON) && VALID_MEMBER(page_slab) &&
- (VALID_MEMBER(page_compound_head) || VALID_MEMBER(page_first_page)))) {
+ (VALID_MEMBER(page_compound_head) || VALID_MEMBER(page_compound_info) ||
+ VALID_MEMBER(page_first_page)))) {
readmem(compound_head(page)+OFFSET(page_slab),
KVADDR, &cache, sizeof(void *),
"page.slab", FAULT_ON_ERROR);
@@ -9904,7 +9920,8 @@ is_slab_overload_page(ulong vaddr, ulong *page_head, char *buf)
if ((vt->flags & SLAB_OVERLOAD_PAGE) &&
is_page_ptr(vaddr, NULL) && VALID_MEMBER(page_slab) &&
- (VALID_MEMBER(page_compound_head) || VALID_MEMBER(page_first_page))) {
+ (VALID_MEMBER(page_compound_head) || VALID_MEMBER(page_compound_info) ||
+ VALID_MEMBER(page_first_page))) {
readmem(compound_head(vaddr)+OFFSET(page_slab),
KVADDR, &cache, sizeof(void *),
"page.slab", FAULT_ON_ERROR);
@@ -9944,7 +9961,8 @@ vaddr_to_slab(ulong vaddr)
slab = 0;
- if ((vt->flags & KMALLOC_SLUB) || VALID_MEMBER(page_compound_head))
+ if ((vt->flags & KMALLOC_SLUB) || VALID_MEMBER(page_compound_head) ||
+ VALID_MEMBER(page_compound_info))
slab = compound_head(page);
else if (vt->flags & SLAB_OVERLOAD_PAGE)
slab = compound_head(page);
@@ -20222,11 +20240,21 @@ get_kmem_cache_child_list(ulong **cache_buf, ulong root)
static ulong
compound_head(ulong page)
{
- ulong flags, first_page, compound_head;
+ ulong flags, first_page, compound_head, info, mask;
first_page = page;
- if (VALID_MEMBER(page_compound_head)) {
+ if (VALID_MEMBER(page_compound_info)) {
+ if (readmem(page + OFFSET(page_compound_info), KVADDR, &info,
+ sizeof(ulong), "page.compound_info", RETURN_ON_ERROR)) {
+ if (compound_info_has_mask) {
+ mask = (info & 1) - 1;
+ mask |= info;
+ first_page = page & mask;
+ } else if (info & 1)
+ first_page = info - 1;
+ }
+ } else if (VALID_MEMBER(page_compound_head)) {
if (readmem(page+OFFSET(page_compound_head), KVADDR, &compound_head,
sizeof(ulong), "page.compound_head", RETURN_ON_ERROR)) {
if (compound_head & 1)
diff --git a/symbols.c b/symbols.c
index 03511c8cbe8c..2071bdf77819 100644
--- a/symbols.c
+++ b/symbols.c
@@ -10504,6 +10504,7 @@ dump_offset_table(char *spec, ulong makestruct)
OFFSET(page_active));
fprintf(fp, " page_compound_head: %ld\n",
OFFSET(page_compound_head));
+ fprintf(fp, " page_compound_info: %ld\n", OFFSET(page_compound_info));
fprintf(fp, " page_private: %ld\n", OFFSET(page_private));
fprintf(fp, " page_page_type: %ld\n",
OFFSET(page_page_type));
--
2.31.1
4 days, 16 hours
[PATCH] kmem: Show page migration types
by Aaron Tomlin
Modify the "kmem" command to query and display the migration type of
pages and page blocks:
- Appending the migration type string (e.g., Unmovable, Movable,
Reclaimable, HighAtomic, CMA, Isolate) to the output of page flags
in kmem -p
- Showing the migration type for each free area sublist in kmem -f
and kmem -F when multiple lists are present per area
To support this, offsets and sizes for mem_section_usage,
mem_section_usage_pageblock_flags, and zone_pageblock_flags are
retrieved and cached. The pageblock flags bitmap is then parsed to
extract the migration type of each page block, matching the logic in the
Linux kernel buddy allocator.
An example excerpt of kmem -f is provided below:
ZONE NAME SIZE FREE MEM_MAP START_PADDR START_MAPNR
2 Normal 3680256 2752702 ffffedd284000000 100000000 1048575
AREA SIZE TYPE FREE_AREA_STRUCT BLOCKS PAGES
0 4k Unmovable ffff8a0b827d5f00 232 232
0 4k Movable ffff8a0b827d5f10 44 44
0 4k Reclaimable ffff8a0b827d5f20 0 0
0 4k HighAtomic ffff8a0b827d5f30 0 0
0 4k CMA ffff8a0b827d5f40 0 0
0 4k Isolate ffff8a0b827d5f50 0 0
1 8k Unmovable ffff8a0b827d5f68 382 764
1 8k Movable ffff8a0b827d5f78 104 208
1 8k Reclaimable ffff8a0b827d5f88 1 2
1 8k HighAtomic ffff8a0b827d5f98 0 0
Signed-off-by: Aaron Tomlin <atomlin(a)atomlin.com>
---
defs.h | 4 +
memory.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++---
symbols.c | 8 ++
3 files changed, 230 insertions(+), 10 deletions(-)
diff --git a/defs.h b/defs.h
index 1ee8744..ec8cc37 100644
--- a/defs.h
+++ b/defs.h
@@ -2108,6 +2108,9 @@ struct offset_table { /* stash of commonly-used offsets */
long memory_block_state;
long memory_block_nid;
long mem_section_pageblock_flags;
+ long mem_section_usage;
+ long mem_section_usage_pageblock_flags;
+ long zone_pageblock_flags;
long bus_type_p;
long device_private_device;
long device_private_knode_bus;
@@ -2389,6 +2392,7 @@ struct size_table { /* stash of commonly-used sizes */
long task_struct_start_time;
long cputime_t;
long mem_section;
+ long mem_section_usage;
long pid_link;
long unwind_table;
long rlimit;
diff --git a/memory.c b/memory.c
index 3f3aa27..7ed59a4 100644
--- a/memory.c
+++ b/memory.c
@@ -171,6 +171,10 @@ static int get_bitfield_data(struct integer_data *);
static int show_page_member_data(char *, ulong, struct meminfo *, char *);
static void dump_mem_map(struct meminfo *);
static void dump_mem_map_SPARSEMEM(struct meminfo *);
+static ulong find_pfn_zone(ulong);
+static ulong get_pageblock_bitmap(ulong);
+static char *get_migratetype_name(int);
+static char *get_page_migration_type_str(physaddr_t);
static void fill_mem_map_cache(ulong, ulong, char *);
static void page_flags_init(void);
static int page_flags_init_from_pageflag_names(void);
@@ -1255,6 +1259,8 @@ vm_init(void)
"zone", "zone_start_pfn");
MEMBER_OFFSET_INIT(zone_spanned_pages,
"zone", "spanned_pages");
+ MEMBER_OFFSET_INIT(zone_pageblock_flags,
+ "zone", "pageblock_flags");
MEMBER_OFFSET_INIT(zone_present_pages,
"zone", "present_pages");
MEMBER_OFFSET_INIT(zone_pages_min,
@@ -6115,10 +6121,19 @@ dump_mem_map_SPARSEMEM(struct meminfo *mi)
bufferindex += sprintflag("%sreserved");
bufferindex += sprintf(outputbuffer+bufferindex, "\n");
} else if (THIS_KERNEL_VERSION > LINUX(2,4,9)) {
- if (vt->flags & PAGEFLAGS)
+ char *mig_type = get_page_migration_type_str(phys);
+ if (vt->flags & PAGEFLAGS) {
bufferindex += translate_page_flags(outputbuffer+bufferindex, flags);
- else
- bufferindex += sprintf(outputbuffer+bufferindex, "%lx\n", flags);
+ if (strlen(mig_type) > 0 && bufferindex > 0 && outputbuffer[bufferindex-1] == '\n') {
+ bufferindex--;
+ bufferindex += sprintf(outputbuffer+bufferindex, " (%s)\n", mig_type);
+ }
+ } else {
+ if (strlen(mig_type) > 0)
+ bufferindex += sprintf(outputbuffer+bufferindex, "%lx (%s)\n", flags, mig_type);
+ else
+ bufferindex += sprintf(outputbuffer+bufferindex, "%lx\n", flags);
+ }
} else {
if ((flags >> v24_PG_locked) & 1)
@@ -6215,6 +6230,170 @@ display_members:
FREEBUF(page_cache);
}
+static ulong
+find_pfn_zone(ulong pfn)
+{
+ int n, i;
+ struct node_table *nt;
+ ulong node_zones;
+ ulong zone_start_pfn = 0, spanned_pages = 0;
+
+ if (INVALID_MEMBER(pglist_data_node_zones) ||
+ INVALID_MEMBER(zone_zone_start_pfn) ||
+ INVALID_MEMBER(zone_spanned_pages))
+ return 0;
+
+ for (n = 0; n < vt->numnodes; n++) {
+ nt = &vt->node_table[n];
+ node_zones = nt->pgdat + OFFSET(pglist_data_node_zones);
+
+ for (i = 0; i < vt->nr_zones; i++) {
+ if (!readmem(node_zones + OFFSET(zone_zone_start_pfn),
+ KVADDR, &zone_start_pfn, sizeof(ulong),
+ "zone zone_start_pfn", RETURN_ON_ERROR|QUIET))
+ goto next_zone;
+ if (!readmem(node_zones + OFFSET(zone_spanned_pages),
+ KVADDR, &spanned_pages, sizeof(ulong),
+ "zone spanned_pages", RETURN_ON_ERROR|QUIET))
+ goto next_zone;
+
+ if (pfn >= zone_start_pfn && pfn < zone_start_pfn + spanned_pages)
+ return node_zones;
+
+next_zone:
+ node_zones += SIZE(zone);
+ }
+ }
+ return 0;
+}
+
+static ulong
+get_pageblock_bitmap(ulong pfn)
+{
+ if (IS_SPARSEMEM()) {
+ ulong section_nr = pfn / PAGES_PER_SECTION();
+ ulong mem_section_addr = nr_to_section(section_nr);
+ if (!mem_section_addr)
+ return 0;
+
+ char *mem_section = read_mem_section(mem_section_addr);
+ if (!mem_section)
+ return 0;
+
+ if (VALID_MEMBER(mem_section_pageblock_flags)) {
+ return ULONG(mem_section + OFFSET(mem_section_pageblock_flags));
+ } else if (VALID_MEMBER(mem_section_usage)) {
+ ulong usage_addr = ULONG(mem_section + OFFSET(mem_section_usage));
+ if (!usage_addr)
+ return 0;
+ return usage_addr + OFFSET(mem_section_usage_pageblock_flags);
+ }
+ } else {
+ ulong zone_addr = find_pfn_zone(pfn);
+ if (zone_addr && VALID_MEMBER(zone_pageblock_flags)) {
+ ulong pageblock_flags_addr = 0;
+ if (readmem(zone_addr + OFFSET(zone_pageblock_flags),
+ KVADDR, &pageblock_flags_addr, sizeof(ulong),
+ "zone pageblock_flags", RETURN_ON_ERROR|QUIET)) {
+ return pageblock_flags_addr;
+ }
+ }
+ }
+ return 0;
+}
+
+static char *
+get_migratetype_name(int migratetype)
+{
+ static char name_buf[64];
+ ulong array_addr, string_addr;
+
+ if (kernel_symbol_exists("migratetype_names")) {
+ array_addr = symbol_value("migratetype_names") + migratetype * sizeof(void *);
+ if (readmem(array_addr, KVADDR, &string_addr, sizeof(void *), "migratetype_names entry", RETURN_ON_ERROR|QUIET)) {
+ if (read_string(string_addr, name_buf, sizeof(name_buf) - 1)) {
+ return name_buf;
+ }
+ }
+ }
+
+ switch (migratetype) {
+ case 0:
+ return "Unmovable";
+ case 1:
+ return "Movable";
+ case 2:
+ return "Reclaimable";
+ case 3:
+ return "HighAtomic";
+ case 4:
+ return "CMA";
+ case 5:
+ return "Isolate";
+ default:
+ sprintf(name_buf, "unknown_%d", migratetype);
+ return name_buf;
+ }
+}
+
+static char *
+get_page_migration_type_str(physaddr_t phys)
+{
+ ulong pfn = phys / PAGESIZE();
+ ulong pageblock_flags_addr = get_pageblock_bitmap(pfn);
+ if (!pageblock_flags_addr)
+ return "";
+
+ int nr_pageblock_bits = 4;
+ long pb_migrate_isolate = 0;
+ if (enumerator_value("PB_migrate_isolate", &pb_migrate_isolate)) {
+ nr_pageblock_bits = 8;
+ }
+
+ unsigned int pageblock_order = 10;
+ if (kernel_symbol_exists("pageblock_order")) {
+ get_symbol_data("pageblock_order", sizeof(unsigned int), &pageblock_order);
+ }
+
+ int bitidx;
+ if (IS_SPARSEMEM()) {
+ bitidx = ((pfn & (PAGES_PER_SECTION() - 1)) >> pageblock_order) * nr_pageblock_bits;
+ } else {
+ ulong zone_addr = find_pfn_zone(pfn);
+ ulong zone_start_pfn = 0;
+ if (zone_addr) {
+ if (!readmem(zone_addr + OFFSET(zone_zone_start_pfn),
+ KVADDR, &zone_start_pfn, sizeof(ulong),
+ "zone zone_start_pfn", RETURN_ON_ERROR|QUIET))
+ zone_start_pfn = 0;
+ }
+ bitidx = ((pfn - zone_start_pfn) >> pageblock_order) * nr_pageblock_bits;
+ }
+
+ ulong word_bitidx = bitidx / (sizeof(ulong) * 8);
+ bitidx &= ((sizeof(ulong) * 8) - 1);
+
+ ulong word_addr = pageblock_flags_addr + word_bitidx * sizeof(ulong);
+ ulong word = 0;
+ if (!readmem(word_addr, KVADDR, &word, sizeof(ulong), "pageblock_flags word", RETURN_ON_ERROR|QUIET)) {
+ return "";
+ }
+
+ int migratetype = (word >> bitidx) & 7;
+ if (nr_pageblock_bits == 8) {
+ int isolate_bit = 4;
+ if ((word >> (bitidx + isolate_bit)) & 1) {
+ long migrate_isolate = 0;
+ if (enumerator_value("MIGRATE_ISOLATE", &migrate_isolate))
+ migratetype = migrate_isolate;
+ else
+ migratetype = 5;
+ }
+ }
+
+ return get_migratetype_name(migratetype);
+}
+
static void
dump_mem_map(struct meminfo *mi)
{
@@ -6566,10 +6745,19 @@ dump_mem_map(struct meminfo *mi)
bufferindex += sprintflag("%sreserved");
bufferindex += sprintf(outputbuffer+bufferindex, "\n");
} else if (THIS_KERNEL_VERSION > LINUX(2,4,9)) {
- if (vt->flags & PAGEFLAGS)
+ char *mig_type = get_page_migration_type_str(phys);
+ if (vt->flags & PAGEFLAGS) {
bufferindex += translate_page_flags(outputbuffer+bufferindex, flags);
- else
- bufferindex += sprintf(outputbuffer+bufferindex, "%lx\n", flags);
+ if (strlen(mig_type) > 0 && bufferindex > 0 && outputbuffer[bufferindex-1] == '\n') {
+ bufferindex--;
+ bufferindex += sprintf(outputbuffer+bufferindex, " (%s)\n", mig_type);
+ }
+ } else {
+ if (strlen(mig_type) > 0)
+ bufferindex += sprintf(outputbuffer+bufferindex, "%lx (%s)\n", flags, mig_type);
+ else
+ bufferindex += sprintf(outputbuffer+bufferindex, "%lx\n", flags);
+ }
} else {
if ((flags >> v24_PG_locked) & 1)
@@ -8412,8 +8600,12 @@ dump_zone_free_area(ulong free_area, int num, ulong verbose,
ld = &list_data;
- if (!verbose)
- fprintf(fp, "%s", free_area_hdr4);
+ if (!verbose) {
+ if (list_count > 1)
+ fprintf(fp, "AREA SIZE TYPE FREE_AREA_STRUCT BLOCKS PAGES\n");
+ else
+ fprintf(fp, "%s", free_area_hdr4);
+ }
total_free = 0;
flen = MAX(VADDR_PRLEN, strlen("FREE_AREA_STRUCT"));
@@ -8487,14 +8679,21 @@ multiple_lists:
for (j = 0, free_list = free_area; j < list_count;
j++, free_list += SIZE(list_head)) {
- if (verbose)
- fprintf(fp, "%s", free_area_hdr3);
+ if (verbose) {
+ if (list_count > 1)
+ fprintf(fp, "AREA SIZE TYPE FREE_AREA_STRUCT\n");
+ else
+ fprintf(fp, "%s", free_area_hdr3);
+ }
fprintf(fp, "%3d ", i);
chunk_size = power(2, i);
sprintf(buf, "%ldk", (chunk_size * PAGESIZE())/1024);
fprintf(fp, " %7s ", buf);
+ if (list_count > 1)
+ fprintf(fp, "%-12s ", get_migratetype_name(j));
+
readmem(free_list, KVADDR, free_list_buf,
SIZE(list_head), "free_area free_list",
FAULT_ON_ERROR);
@@ -17722,6 +17921,15 @@ sparse_mem_init(void)
MEMBER_OFFSET_INIT(mem_section_section_mem_map, "mem_section",
"section_mem_map");
+ if (MEMBER_EXISTS("mem_section", "pageblock_flags")) {
+ MEMBER_OFFSET_INIT(mem_section_pageblock_flags, "mem_section",
+ "pageblock_flags");
+ }
+ if (MEMBER_EXISTS("mem_section", "usage")) {
+ MEMBER_OFFSET_INIT(mem_section_usage, "mem_section", "usage");
+ STRUCT_SIZE_INIT(mem_section_usage, "mem_section_usage");
+ MEMBER_OFFSET_INIT(mem_section_usage_pageblock_flags, "mem_section_usage", "pageblock_flags");
+ }
if (!MAX_PHYSMEM_BITS())
error(FATAL,
diff --git a/symbols.c b/symbols.c
index 78e400b..2e3a797 100644
--- a/symbols.c
+++ b/symbols.c
@@ -11416,6 +11416,12 @@ dump_offset_table(char *spec, ulong makestruct)
OFFSET(mem_section_section_mem_map));
fprintf(fp, " mem_section_pageblock_flags: %ld\n",
OFFSET(mem_section_pageblock_flags));
+ fprintf(fp, " mem_section_usage: %ld\n",
+ OFFSET(mem_section_usage));
+ fprintf(fp, "mem_section_usage_pageblock_flags: %ld\n",
+ OFFSET(mem_section_usage_pageblock_flags));
+ fprintf(fp, " zone_pageblock_flags: %ld\n",
+ OFFSET(zone_pageblock_flags));
fprintf(fp, " memory_block_dev: %ld\n",
OFFSET(memory_block_dev));
fprintf(fp, " memory_block_nid: %ld\n",
@@ -12158,6 +12164,8 @@ dump_offset_table(char *spec, ulong makestruct)
SIZE(cputime_t));
fprintf(fp, " mem_section: %ld\n",
SIZE(mem_section));
+ fprintf(fp, " mem_section_usage: %ld\n",
+ SIZE(mem_section_usage));
fprintf(fp, " pid_link: %ld\n",
SIZE(pid_link));
fprintf(fp, " upid: %ld\n",
--
2.55.0
1 week, 1 day
[PATCH] Add linux version 7.x in verify_namelist
by Dave Young
In case linux banner can not be found in vmlinux for some reason.
The fallback chcking of "Linux version" failed for kernel 7.x with
below error msg:
WARNING: kernel version inconsistency between vmlinux and dumpfile
crash: incompatible arguments: vmlinux is not SMP -- vmcore is SMP
Not sure how to reproduce, tested by forcing get_linux_banner_from_vmlinux to return false.
Add "Linux version 7" to fix it.
Fixes: https://github.com/crash-utility/crash/issues/232
Reported-by: Jiri Slaby <jirislaby(a)gmail.com>
Signed-off-by: Dave Young <yangrr.2009(a)tsinghua.org.cn>
---
kernel.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
Index: crash/kernel.c
===================================================================
--- crash.orig/kernel.c 2026-08-13 13:26:26.858739906 +0800
+++ crash/kernel.c 2026-08-13 13:27:30.450848208 +0800
@@ -1400,7 +1400,8 @@
!strstr(buffer, "Linux version 3.") &&
!strstr(buffer, "Linux version 4.") &&
!strstr(buffer, "Linux version 5.") &&
- !strstr(buffer, "Linux version 6."))
+ !strstr(buffer, "Linux version 6.") &&
+ !strstr(buffer, "Linux version 7."))
continue;
if (strstr(buffer, kt->proc_version)) {
1 week, 2 days
Laos Visa for Business Travellers
by Edgar Sullivan
The Laos eVisa for Business Travellers is a topic of interest for professionals planning meetings, conferences, networking activities, or commercial visits in Laos. However, travelers should not assume that the standard Laos eVisa covers all business-related activities. The appropriate visa category depends on the purpose, duration, and nature of the planned stay. Business visitors may need a separate business visa or additional authorization from the relevant Lao authorities. Before submitting an application, travelers should confirm the correct visa type and prepare any required business documentation, such as an invitation or company-related information. Checking current Lao immigration rules, fees, processing times, and entry requirements is recommended before travel.
website: https://lao-evisa.com/laos-visa-for-business-travelers/
1 week, 3 days
Application Process For A Laos eVisa
by Edgar Sullivan
The <a href="https://lao-evisa.com/application-process-for-laos-e-visa/">Application Process for a Laos eVisa</a> begins with checking whether the traveler and intended trip meet the current eligibility conditions. Eligible applicants can complete the online form by entering their personal details, passport information, contact details, and travel plans. A recent photograph and a digital copy of the passport may be required during submission. After completing the form, applicants should carefully verify all information before paying the applicable fee and submitting the request. The application is then reviewed by the relevant authorities. If approved, the eVisa document is provided electronically. Travelers should save or print the approval and confirm the latest entry requirements before traveling to Laos.
1 week, 3 days
[PATCH 1/2] add cpu_to_nid function
by Huang Shijie
Add cpu_to_nid function which we can use to get the NUMA node id
by the cpu id.
1.) For live mode: use /sys/ files to get the cpu_to_nid.
This method will succeed 100%.
2.) For vmcore mode: use "numa_node" symbol to get the cpu_to_nid.
If CONFIG_USE_PERCPU_NUMA_NODE_ID does not defined in kernel,
this method will fail.
Signed-off-by: Huang Shijie <huangsj(a)hygon.cn>
---
defs.h | 2 ++
kernel.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
main.c | 1 +
3 files changed, 69 insertions(+)
diff --git a/defs.h b/defs.h
index e3027e2..7e1515a 100644
--- a/defs.h
+++ b/defs.h
@@ -6044,6 +6044,8 @@ ulong do_maple_tree(ulong, int, struct list_pair *);
void help_init(void);
void cmd_usage(char *, int);
void display_version(void);
+int cpu_to_nid(int cpu);
+void numa_init(void);
void display_help_screen(char *);
#ifdef ARM
#define dump_machdep_table(X) arm_dump_machdep_table(X)
diff --git a/kernel.c b/kernel.c
index e53038d..32230fb 100644
--- a/kernel.c
+++ b/kernel.c
@@ -12221,3 +12221,69 @@ out:
pc->error_fp = error_fp_save;
}
#endif
+
+static int *cpu_to_nid_map;
+
+int
+cpu_to_nid(int cpu)
+{
+ if (!cpu_to_nid_map || cpu < 0 || cpu >= kt->cpus)
+ return -1;
+ if (vt->numnodes == 1)
+ return 0;
+ return cpu_to_nid_map[cpu];
+}
+
+static void
+cpu_to_nid_init(void)
+{
+ int i, j;
+ int fd;
+ char buf[64];
+
+ cpu_to_nid_map = malloc(kt->cpus * sizeof(int));
+ if (vt->numnodes == 1)
+ return;
+
+ memset(cpu_to_nid_map, -1, kt->cpus * sizeof(int));
+
+ if (ACTIVE()) {
+ for (i = 0; i < kt->cpus; i++) {
+ for (j = 0; j < vt->numnodes; j++) {
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "/sys/devices/system/cpu/cpu%d/node%d", i, j);
+
+ fd = open(buf, O_RDONLY);
+ if (fd > 0) {
+ cpu_to_nid_map[i] = j;
+ close(fd);
+ break;
+ }
+ }
+ }
+ } else {
+ int cpu;
+
+ if (symbol_exists("numa_node")) {
+ ulong base = symbol_value("numa_node");
+
+ for (cpu = 0; cpu < kt->cpus; cpu++) {
+ ulong addr = base + kt->__per_cpu_offset[cpu];
+ int nid;
+
+ if (readmem(addr, KVADDR, &nid, sizeof(int),
+ "numa_node", RETURN_ON_ERROR|QUIET))
+ cpu_to_nid_map[cpu] = nid;
+ }
+ } else {
+ error(WARNING,
+ "numa_node symbol not found in vmcore\n");
+ }
+ }
+}
+
+void
+numa_init(void)
+{
+ cpu_to_nid_init();
+}
diff --git a/main.c b/main.c
index d5f8486..a8c77f8 100644
--- a/main.c
+++ b/main.c
@@ -793,6 +793,7 @@ main_loop(void)
kernel_init();
machdep_init(POST_GDB);
vm_init();
+ numa_init();
machdep_init(POST_VM);
module_init();
help_init();
--
2.53.0
1 week, 4 days
[PATCH v3] symbols: optimize symval_hash_init with O(1) tail insertion
by Rui Qi
Replace the O(n) tail traversal with O(1) tail insertion using a
dynamically-allocated per-bucket tail tracking array. The original code
traversed the entire linked list on every insert to find the tail,
resulting in O(n^2) complexity for hash table initialization.
This reduces hash table initialization from O(n^2) to O(n).
Benchmark on an x86_64 machine (kernel 5.10.135, ~112k symbols):
Before: 5.54 s (mean, n=6, sigma=0.12)
After: 5.31 s (mean, n=6, sigma=0.12)
Speedup: 1.04x (-4.1%)
Benchmark on an ARM64 Neoverse-N2 machine (kernel 5.15.152 arm64,
~132k nm symbols):
Before: 3.4113 s (mean, n=48, sigma=0.0698)
After: 2.5073 s (mean, n=48, sigma=0.0394)
Speedup: 1.36x (-26.5%), with improved consistency.
User CPU: 4.1706 s -> 3.2768 s (-21.4%).
Benchmark retest on a RISC-V machine (kernel
6.12.13 riscv64, ~198k nm symbols):
$ printf 'q\n' | ./crash vmlinux /proc/kcore
Before: 11.934 s (mean, n=3, sigma=0.880; samples: 12.921, 11.231, 11.651)
After: 10.255 s (mean, n=3, sigma=0.835; samples: 11.131, 9.467, 10.168)
Speedup: 1.16x (-14.1%)
Signed-off-by: Rui Qi <qirui.001(a)bytedance.com>
Reviewed-by: Dave Young <yangrr.2009(a)tsinghua.org.cn>
---
Changes since v2:
- Fix the comment to reference the dynamically-allocated tails[]
array instead of val_hash_last (suggested by Dave Young)
- Use malloc/free for the tails[] array instead of stack allocation
(suggested by Dave Young)
Changes from V1 [1]:
- Use a separate local tails[] array for tail tracking instead of reusing
val_hash_last, to preserve its original semantics as a last-visited-entry
cache for symval_hash_search(). (Dave Young, Tao Liu)
- Add benchmarks on x86_64 and ARM64 in addition to RISC-V. (Tao Liu)
symbols.c | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/symbols.c b/symbols.c
index 03511c8cbe8c..2859afa7f6e0 100644
--- a/symbols.c
+++ b/symbols.c
@@ -1085,12 +1085,18 @@ symbol_value_from_proc_kallsyms(char *symname)
/*
* Install all static kernel symbol values into the symval_hash.
+ * Uses a dynamically-allocated tails[] array for O(1) tail insertion.
*/
static void
symval_hash_init(void)
{
int index;
- struct syment *sp, *sph;
+ struct syment *sp, **tails;
+
+ tails = (struct syment **)malloc(SYMVAL_HASH * sizeof(struct syment *));
+ if (tails == NULL)
+ error(FATAL, "symval_hash_init tails malloc: %s\n", strerror(errno));
+ BZERO(tails, SYMVAL_HASH * sizeof(struct syment *));
for (sp = st->symtable; sp < st->symend; sp++) {
index = SYMVAL_HASH_INDEX(sp->value);
@@ -1098,15 +1104,12 @@ symval_hash_init(void)
if (st->symval_hash[index].val_hash_head == NULL) {
st->symval_hash[index].val_hash_head = sp;
st->symval_hash[index].val_hash_last = sp;
- continue;
- }
-
- sph = st->symval_hash[index].val_hash_head;
- while (sph->val_hash_next)
- sph = sph->val_hash_next;
-
- sph->val_hash_next = sp;
+ } else
+ tails[index]->val_hash_next = sp;
+ tails[index] = sp;
}
+
+ free(tails);
}
/*
--
2.20.1
1 week, 4 days