|
|
58c03c |
commit 6596f1121b89162f96d1e1825c2905b83b59bec1
|
|
|
58c03c |
Author: Dave Anderson <anderson@redhat.com>
|
|
|
58c03c |
Date: Wed Jul 11 16:25:59 2018 -0400
|
|
|
58c03c |
|
|
|
58c03c |
The existing "list" command uses a hash table to detect duplicate
|
|
|
58c03c |
items as it traverses the list. The hash table approach has worked
|
|
|
58c03c |
well for many years. However, with increasing memory sizes and list
|
|
|
58c03c |
sizes, the overhead of the hash table can be substantial, often
|
|
|
58c03c |
leading to commands running for a very long time. For large lists,
|
|
|
58c03c |
we have found that the existing hash based approach may slow the
|
|
|
58c03c |
system to a crawl and possibly never complete. You can turn off
|
|
|
58c03c |
the hash with "set hash off" but then there is no loop detection; in
|
|
|
58c03c |
that case, loop detection must be done manually after dumping the
|
|
|
58c03c |
list to disk or some other method. This patch is an implementation
|
|
|
58c03c |
of the cycle detection algorithm from R. P. Brent as an alternative
|
|
|
58c03c |
algorithm for the "list" command. The algorithm both avoids the
|
|
|
58c03c |
overhead of the hash table and yet is able to detect a loop. In
|
|
|
58c03c |
addition, further loop characteristics are printed, such as the
|
|
|
58c03c |
distance to the start of the loop as well as the loop length.
|
|
|
58c03c |
An excellent description of the algorithm can be found here on
|
|
|
58c03c |
the crash-utility mailing list:
|
|
|
58c03c |
|
|
|
58c03c |
https://www.redhat.com/archives/crash-utility/2018-July/msg00019.html
|
|
|
58c03c |
|
|
|
58c03c |
A new "list -B" option has been added to the "list" command to
|
|
|
58c03c |
invoke this new algorithm rather than using the hash table. In
|
|
|
58c03c |
addition to low memory usage, the output of the list command is
|
|
|
58c03c |
slightly different when a loop is detected. In addition to printing
|
|
|
58c03c |
the first duplicate entry, the length of the loop, and the distance
|
|
|
58c03c |
to the loop is output.
|
|
|
58c03c |
(dwysocha@redhat.com)
|
|
|
58c03c |
|
|
|
58c03c |
diff --git a/defs.h b/defs.h
|
|
|
58c03c |
index b05aecc..5af82be 100644
|
|
|
58c03c |
--- a/defs.h
|
|
|
58c03c |
+++ b/defs.h
|
|
|
58c03c |
@@ -2491,6 +2491,7 @@ struct list_data { /* generic structure used by do_list() to walk */
|
|
|
58c03c |
#define CALLBACK_RETURN (VERBOSE << 12)
|
|
|
58c03c |
#define LIST_PARSE_MEMBER (VERBOSE << 13)
|
|
|
58c03c |
#define LIST_READ_MEMBER (VERBOSE << 14)
|
|
|
58c03c |
+#define LIST_BRENT_ALGO (VERBOSE << 15)
|
|
|
58c03c |
|
|
|
58c03c |
struct tree_data {
|
|
|
58c03c |
ulong flags;
|
|
|
58c03c |
@@ -4944,6 +4945,7 @@ char *shift_string_right(char *, int);
|
|
|
58c03c |
int bracketed(char *, char *, int);
|
|
|
58c03c |
void backspace(int);
|
|
|
58c03c |
int do_list(struct list_data *);
|
|
|
58c03c |
+int do_list_no_hash(struct list_data *);
|
|
|
58c03c |
struct radix_tree_ops {
|
|
|
58c03c |
void (*entry)(ulong node, ulong slot, const char *path,
|
|
|
58c03c |
ulong index, void *private);
|
|
|
58c03c |
diff --git a/help.c b/help.c
|
|
|
58c03c |
index 638c6ec..54bf9b4 100644
|
|
|
58c03c |
--- a/help.c
|
|
|
58c03c |
+++ b/help.c
|
|
|
58c03c |
@@ -5724,7 +5724,7 @@ char *help__list[] = {
|
|
|
58c03c |
"list",
|
|
|
58c03c |
"linked list",
|
|
|
58c03c |
"[[-o] offset][-e end][-[s|S] struct[.member[,member] [-l offset]] -[x|d]]"
|
|
|
58c03c |
-"\n [-r|-h|-H] start",
|
|
|
58c03c |
+"\n [-r|-B] [-h|-H] start",
|
|
|
58c03c |
" ",
|
|
|
58c03c |
" This command dumps the contents of a linked list. The entries in a linked",
|
|
|
58c03c |
" list are typically data structures that are tied together in one of two",
|
|
|
58c03c |
@@ -5822,6 +5822,12 @@ char *help__list[] = {
|
|
|
58c03c |
" -r For a list linked with list_head structures, traverse the list",
|
|
|
58c03c |
" in the reverse order by using the \"prev\" pointer instead",
|
|
|
58c03c |
" of \"next\".",
|
|
|
58c03c |
+" -B Use the algorithm from R. P. Brent to detect loops instead of",
|
|
|
58c03c |
+" using a hash table. This algorithm uses a tiny fixed amount of",
|
|
|
58c03c |
+" memory and so is especially helpful for longer lists. The output",
|
|
|
58c03c |
+" is slightly different than the normal list output as it will",
|
|
|
58c03c |
+" print the length of the loop, the start of the loop, and the",
|
|
|
58c03c |
+" first duplicate in the list.",
|
|
|
58c03c |
" ",
|
|
|
58c03c |
" The meaning of the \"start\" argument, which can be expressed symbolically,",
|
|
|
58c03c |
" in hexadecimal format, or an expression evaluating to an address, depends",
|
|
|
58c03c |
diff --git a/tools.c b/tools.c
|
|
|
58c03c |
index 1a83643..634aec6 100644
|
|
|
58c03c |
--- a/tools.c
|
|
|
58c03c |
+++ b/tools.c
|
|
|
58c03c |
@@ -3266,9 +3266,12 @@ cmd_list(void)
|
|
|
58c03c |
BZERO(ld, sizeof(struct list_data));
|
|
|
58c03c |
struct_list_offset = 0;
|
|
|
58c03c |
|
|
|
58c03c |
- while ((c = getopt(argcnt, args, "Hhrs:S:e:o:xdl:")) != EOF) {
|
|
|
58c03c |
+ while ((c = getopt(argcnt, args, "BHhrs:S:e:o:xdl:")) != EOF) {
|
|
|
58c03c |
switch(c)
|
|
|
58c03c |
{
|
|
|
58c03c |
+ case 'B':
|
|
|
58c03c |
+ ld->flags |= LIST_BRENT_ALGO;
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
case 'H':
|
|
|
58c03c |
ld->flags |= LIST_HEAD_FORMAT;
|
|
|
58c03c |
ld->flags |= LIST_HEAD_POINTER;
|
|
|
58c03c |
@@ -3516,9 +3519,13 @@ next_arg:
|
|
|
58c03c |
ld->flags &= ~(LIST_OFFSET_ENTERED|LIST_START_ENTERED);
|
|
|
58c03c |
ld->flags |= VERBOSE;
|
|
|
58c03c |
|
|
|
58c03c |
- hq_open();
|
|
|
58c03c |
- c = do_list(ld);
|
|
|
58c03c |
- hq_close();
|
|
|
58c03c |
+ if (ld->flags & LIST_BRENT_ALGO)
|
|
|
58c03c |
+ c = do_list_no_hash(ld);
|
|
|
58c03c |
+ else {
|
|
|
58c03c |
+ hq_open();
|
|
|
58c03c |
+ c = do_list(ld);
|
|
|
58c03c |
+ hq_close();
|
|
|
58c03c |
+ }
|
|
|
58c03c |
|
|
|
58c03c |
if (ld->structname_args)
|
|
|
58c03c |
FREEBUF(ld->structname);
|
|
|
58c03c |
@@ -3862,6 +3869,283 @@ do_list(struct list_data *ld)
|
|
|
58c03c |
return count;
|
|
|
58c03c |
}
|
|
|
58c03c |
|
|
|
58c03c |
+static void
|
|
|
58c03c |
+do_list_debug_entry(struct list_data *ld)
|
|
|
58c03c |
+{
|
|
|
58c03c |
+ int i, others;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (CRASHDEBUG(1)) {
|
|
|
58c03c |
+ others = 0;
|
|
|
58c03c |
+ console(" flags: %lx (", ld->flags);
|
|
|
58c03c |
+ if (ld->flags & VERBOSE)
|
|
|
58c03c |
+ console("%sVERBOSE", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_OFFSET_ENTERED)
|
|
|
58c03c |
+ console("%sLIST_OFFSET_ENTERED", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_START_ENTERED)
|
|
|
58c03c |
+ console("%sLIST_START_ENTERED", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_HEAD_FORMAT)
|
|
|
58c03c |
+ console("%sLIST_HEAD_FORMAT", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_HEAD_POINTER)
|
|
|
58c03c |
+ console("%sLIST_HEAD_POINTER", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & RETURN_ON_DUPLICATE)
|
|
|
58c03c |
+ console("%sRETURN_ON_DUPLICATE", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & RETURN_ON_LIST_ERROR)
|
|
|
58c03c |
+ console("%sRETURN_ON_LIST_ERROR", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & RETURN_ON_LIST_ERROR)
|
|
|
58c03c |
+ console("%sRETURN_ON_LIST_ERROR", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_STRUCT_RADIX_10)
|
|
|
58c03c |
+ console("%sLIST_STRUCT_RADIX_10", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_STRUCT_RADIX_16)
|
|
|
58c03c |
+ console("%sLIST_STRUCT_RADIX_16", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_ALLOCATE)
|
|
|
58c03c |
+ console("%sLIST_ALLOCATE", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & LIST_CALLBACK)
|
|
|
58c03c |
+ console("%sLIST_CALLBACK", others++ ? "|" : "");
|
|
|
58c03c |
+ if (ld->flags & CALLBACK_RETURN)
|
|
|
58c03c |
+ console("%sCALLBACK_RETURN", others++ ? "|" : "");
|
|
|
58c03c |
+ console(")\n");
|
|
|
58c03c |
+ console(" start: %lx\n", ld->start);
|
|
|
58c03c |
+ console(" member_offset: %ld\n", ld->member_offset);
|
|
|
58c03c |
+ console(" list_head_offset: %ld\n", ld->list_head_offset);
|
|
|
58c03c |
+ console(" end: %lx\n", ld->end);
|
|
|
58c03c |
+ console(" searchfor: %lx\n", ld->searchfor);
|
|
|
58c03c |
+ console(" structname_args: %lx\n", ld->structname_args);
|
|
|
58c03c |
+ if (!ld->structname_args)
|
|
|
58c03c |
+ console(" structname: (unused)\n");
|
|
|
58c03c |
+ for (i = 0; i < ld->structname_args; i++)
|
|
|
58c03c |
+ console(" structname[%d]: %s\n", i, ld->structname[i]);
|
|
|
58c03c |
+ console(" header: %s\n", ld->header);
|
|
|
58c03c |
+ console(" list_ptr: %lx\n", (ulong)ld->list_ptr);
|
|
|
58c03c |
+ console(" callback_func: %lx\n", (ulong)ld->callback_func);
|
|
|
58c03c |
+ console(" callback_data: %lx\n", (ulong)ld->callback_data);
|
|
|
58c03c |
+ console("struct_list_offset: %lx\n", ld->struct_list_offset);
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+}
|
|
|
58c03c |
+
|
|
|
58c03c |
+
|
|
|
58c03c |
+static void
|
|
|
58c03c |
+do_list_output_struct(struct list_data *ld, ulong next, ulong offset,
|
|
|
58c03c |
+ unsigned int radix, struct req_entry **e)
|
|
|
58c03c |
+{
|
|
|
58c03c |
+ int i;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ for (i = 0; i < ld->structname_args; i++) {
|
|
|
58c03c |
+ switch (count_chars(ld->structname[i], '.'))
|
|
|
58c03c |
+ {
|
|
|
58c03c |
+ case 0:
|
|
|
58c03c |
+ dump_struct(ld->structname[i],
|
|
|
58c03c |
+ next - offset, radix);
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ default:
|
|
|
58c03c |
+ if (ld->flags & LIST_PARSE_MEMBER)
|
|
|
58c03c |
+ dump_struct_members(ld, i, next);
|
|
|
58c03c |
+ else if (ld->flags & LIST_READ_MEMBER)
|
|
|
58c03c |
+ dump_struct_members_fast(e[i],
|
|
|
58c03c |
+ radix, next - offset);
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+}
|
|
|
58c03c |
+
|
|
|
58c03c |
+static int
|
|
|
58c03c |
+do_list_no_hash_readmem(struct list_data *ld, ulong *next_ptr,
|
|
|
58c03c |
+ ulong readflag)
|
|
|
58c03c |
+{
|
|
|
58c03c |
+ if (!readmem(*next_ptr + ld->member_offset, KVADDR, next_ptr,
|
|
|
58c03c |
+ sizeof(void *), "list entry", readflag)) {
|
|
|
58c03c |
+ error(INFO, "\ninvalid list entry: %lx\n", *next_ptr);
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ return 0;
|
|
|
58c03c |
+}
|
|
|
58c03c |
+
|
|
|
58c03c |
+static ulong brent_x; /* tortoise */
|
|
|
58c03c |
+static ulong brent_y; /* hare */
|
|
|
58c03c |
+static ulong brent_r; /* power */
|
|
|
58c03c |
+static ulong brent_lambda; /* loop length */
|
|
|
58c03c |
+static ulong brent_mu; /* distance to start of loop */
|
|
|
58c03c |
+static ulong brent_loop_detect;
|
|
|
58c03c |
+static ulong brent_loop_exit;
|
|
|
58c03c |
+/*
|
|
|
58c03c |
+ * 'ptr': representative of x or y; modified on return
|
|
|
58c03c |
+ */
|
|
|
58c03c |
+static int
|
|
|
58c03c |
+brent_f(ulong *ptr, struct list_data *ld, ulong readflag)
|
|
|
58c03c |
+{
|
|
|
58c03c |
+ return do_list_no_hash_readmem(ld, ptr, readflag);
|
|
|
58c03c |
+}
|
|
|
58c03c |
+
|
|
|
58c03c |
+/*
|
|
|
58c03c |
+ * Similar to do_list() but without the hash_table or LIST_ALLOCATE.
|
|
|
58c03c |
+ * Useful for the 'list' command and other callers needing faster list
|
|
|
58c03c |
+ * enumeration.
|
|
|
58c03c |
+ */
|
|
|
58c03c |
+int
|
|
|
58c03c |
+do_list_no_hash(struct list_data *ld)
|
|
|
58c03c |
+{
|
|
|
58c03c |
+ ulong next, last, first, offset;
|
|
|
58c03c |
+ ulong searchfor, readflag;
|
|
|
58c03c |
+ int i, count, ret;
|
|
|
58c03c |
+ unsigned int radix;
|
|
|
58c03c |
+ struct req_entry **e = NULL;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ do_list_debug_entry(ld);
|
|
|
58c03c |
+
|
|
|
58c03c |
+ count = 0;
|
|
|
58c03c |
+ searchfor = ld->searchfor;
|
|
|
58c03c |
+ ld->searchfor = 0;
|
|
|
58c03c |
+ if (ld->flags & LIST_STRUCT_RADIX_10)
|
|
|
58c03c |
+ radix = 10;
|
|
|
58c03c |
+ else if (ld->flags & LIST_STRUCT_RADIX_16)
|
|
|
58c03c |
+ radix = 16;
|
|
|
58c03c |
+ else
|
|
|
58c03c |
+ radix = 0;
|
|
|
58c03c |
+ next = ld->start;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ readflag = ld->flags & RETURN_ON_LIST_ERROR ?
|
|
|
58c03c |
+ (RETURN_ON_ERROR|QUIET) : FAULT_ON_ERROR;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (!readmem(next + ld->member_offset, KVADDR, &first, sizeof(void *),
|
|
|
58c03c |
+ "first list entry", readflag)) {
|
|
|
58c03c |
+ error(INFO, "\ninvalid list entry: %lx\n", next);
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (ld->header)
|
|
|
58c03c |
+ fprintf(fp, "%s", ld->header);
|
|
|
58c03c |
+
|
|
|
58c03c |
+ offset = ld->list_head_offset + ld->struct_list_offset;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (ld->structname && (ld->flags & LIST_READ_MEMBER)) {
|
|
|
58c03c |
+ e = (struct req_entry **)GETBUF(sizeof(*e) * ld->structname_args);
|
|
|
58c03c |
+ for (i = 0; i < ld->structname_args; i++)
|
|
|
58c03c |
+ e[i] = fill_member_offsets(ld->structname[i]);
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ brent_loop_detect = brent_loop_exit = 0;
|
|
|
58c03c |
+ brent_lambda = 0;
|
|
|
58c03c |
+ brent_r = 2;
|
|
|
58c03c |
+ brent_x = brent_y = next;
|
|
|
58c03c |
+ ret = brent_f(&brent_y, ld, readflag);
|
|
|
58c03c |
+ if (ret == -1)
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ while (1) {
|
|
|
58c03c |
+ if (!brent_loop_detect && ld->flags & VERBOSE) {
|
|
|
58c03c |
+ fprintf(fp, "%lx\n", next - ld->list_head_offset);
|
|
|
58c03c |
+ if (ld->structname) {
|
|
|
58c03c |
+ do_list_output_struct(ld, next, offset, radix, e);
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (next && brent_loop_exit) {
|
|
|
58c03c |
+ if (ld->flags &
|
|
|
58c03c |
+ (RETURN_ON_DUPLICATE|RETURN_ON_LIST_ERROR)) {
|
|
|
58c03c |
+ error(INFO, "\nduplicate list entry: %lx\n",
|
|
|
58c03c |
+ brent_x);
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ error(FATAL, "\nduplicate list entry: %lx\n", brent_x);
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if ((searchfor == next) ||
|
|
|
58c03c |
+ (searchfor == (next - ld->list_head_offset)))
|
|
|
58c03c |
+ ld->searchfor = searchfor;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ count++;
|
|
|
58c03c |
+ last = next;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if ((ld->flags & LIST_CALLBACK) &&
|
|
|
58c03c |
+ ld->callback_func((void *)(next - ld->list_head_offset),
|
|
|
58c03c |
+ ld->callback_data) && (ld->flags & CALLBACK_RETURN))
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ ret = do_list_no_hash_readmem(ld, &next, readflag);
|
|
|
58c03c |
+ if (ret == -1)
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (!brent_loop_detect) {
|
|
|
58c03c |
+ if (brent_x == brent_y) {
|
|
|
58c03c |
+ brent_loop_detect = 1;
|
|
|
58c03c |
+ error(INFO, "loop detected, loop length: %lx\n", brent_lambda);
|
|
|
58c03c |
+ /* reset x and y to start; advance y loop length */
|
|
|
58c03c |
+ brent_mu = 0;
|
|
|
58c03c |
+ brent_x = brent_y = ld->start;
|
|
|
58c03c |
+ while (brent_lambda--) {
|
|
|
58c03c |
+ ret = brent_f(&brent_y, ld, readflag);
|
|
|
58c03c |
+ if (ret == -1)
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ } else {
|
|
|
58c03c |
+ if (brent_r == brent_lambda) {
|
|
|
58c03c |
+ brent_x = brent_y;
|
|
|
58c03c |
+ brent_r *= 2;
|
|
|
58c03c |
+ brent_lambda = 0;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ brent_y = next;
|
|
|
58c03c |
+ brent_lambda++;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ } else {
|
|
|
58c03c |
+ if (!brent_loop_exit && brent_x == brent_y) {
|
|
|
58c03c |
+ brent_loop_exit = 1;
|
|
|
58c03c |
+ error(INFO, "length from start to loop: %lx",
|
|
|
58c03c |
+ brent_mu);
|
|
|
58c03c |
+ } else {
|
|
|
58c03c |
+ ret = brent_f(&brent_x, ld, readflag);
|
|
|
58c03c |
+ if (ret == -1)
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ ret = brent_f(&brent_y, ld, readflag);
|
|
|
58c03c |
+ if (ret == -1)
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ brent_mu++;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (next == 0) {
|
|
|
58c03c |
+ if (ld->flags & LIST_HEAD_FORMAT) {
|
|
|
58c03c |
+ error(INFO, "\ninvalid list entry: 0\n");
|
|
|
58c03c |
+ return -1;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ if (CRASHDEBUG(1))
|
|
|
58c03c |
+ console("do_list end: next:%lx\n", next);
|
|
|
58c03c |
+
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (next == ld->end) {
|
|
|
58c03c |
+ if (CRASHDEBUG(1))
|
|
|
58c03c |
+ console("do_list end: next:%lx == end:%lx\n",
|
|
|
58c03c |
+ next, ld->end);
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (next == ld->start) {
|
|
|
58c03c |
+ if (CRASHDEBUG(1))
|
|
|
58c03c |
+ console("do_list end: next:%lx == start:%lx\n",
|
|
|
58c03c |
+ next, ld->start);
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (next == last) {
|
|
|
58c03c |
+ if (CRASHDEBUG(1))
|
|
|
58c03c |
+ console("do_list end: next:%lx == last:%lx\n",
|
|
|
58c03c |
+ next, last);
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if ((next == first) && (count != 1)) {
|
|
|
58c03c |
+ if (CRASHDEBUG(1))
|
|
|
58c03c |
+ console("do_list end: next:%lx == first:%lx (count %d)\n",
|
|
|
58c03c |
+ next, last, count);
|
|
|
58c03c |
+ break;
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+ }
|
|
|
58c03c |
+
|
|
|
58c03c |
+ if (CRASHDEBUG(1))
|
|
|
58c03c |
+ console("do_list count: %d\n", count);
|
|
|
58c03c |
+
|
|
|
58c03c |
+ return count;
|
|
|
58c03c |
+}
|
|
|
58c03c |
+
|
|
|
58c03c |
/*
|
|
|
58c03c |
* Issue a dump_struct_member() call for one or more structure
|
|
|
58c03c |
* members. Multiple members are passed in a comma-separated
|