[PATCH 3/11] Implement percpu variables for "p"

Add per-cpu specifications to the "p" command. This allows to specify
CPUs for a per-cpu symbol after a colon.

Keep in mind that a colon can be part of a valid C expression, e.g. ':'
or the ternary operator, so if the part before the colon is not a symbol,
the colon must be restored. Fortunately, a symbol name followed by a colon
cannot start a valid C expression, so there is never any ambiguity.

Signed-off-by: Petr Tesarik <ptesarik@suse.cz>

---
 help.c    |   14 +++++++
 symbols.c |  113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 115 insertions(+), 12 deletions(-)

--- a/symbols.c
+++ b/symbols.c
@@ -73,7 +73,7 @@ static void Elf32_Sym_to_common(Elf32_Sy
 static void Elf64_Sym_to_common(Elf64_Sym *, struct elf_common *); 
 static void cmd_datatype_common(ulong);
 static void process_gdb_output(char *, unsigned, const char *, int);
-static int display_per_cpu_info(struct syment *);
+static int display_per_cpu_info(struct syment *, int, char *);
 static struct load_module *get_module_percpu_sym_owner(struct syment *);
 static int is_percpu_symbol(struct syment *);
 static void dump_percpu_symbols(struct load_module *);
@@ -6392,6 +6392,7 @@ cmd_p(void)
 	unsigned radix;
 	int do_load_module_filter;
 	char buf1[BUFSIZE]; 
+	char *cpuspec;
 
 	do_load_module_filter = radix = 0;
 
@@ -6426,19 +6427,32 @@ cmd_p(void)
         if (argerrs || !args[optind])
                 cmd_usage(pc->curcmd, SYNOPSIS);
 
+	cpuspec = strchr(args[optind], ':');
+	if (cpuspec)
+		*cpuspec++ = NULLCHAR;
+
 	sp = NULL;
 	if ((sp = symbol_search(args[optind])) && !args[optind+1]) {
 		if ((percpu_sp = per_cpu_symbol_search(args[optind])) &&
-		    display_per_cpu_info(percpu_sp))
+		    display_per_cpu_info(percpu_sp, radix, cpuspec))
 			return;
 		if (module_symbol(sp->value, NULL, NULL, NULL, *gdb_output_radix))
 			do_load_module_filter = TRUE;
 	} else if ((percpu_sp = per_cpu_symbol_search(args[optind])) &&
-		   display_per_cpu_info(percpu_sp))
+		   display_per_cpu_info(percpu_sp, radix, cpuspec))
 		return;
 	else if (st->flags & LOAD_MODULE_SYMS)
 		do_load_module_filter = TRUE;
 
+	if (cpuspec) {
+		if (sp)
+			error(WARNING, "%s is not percpu; cpuspec ignored.\n",
+			      sp->name);
+		else
+			/* maybe a valid C expression (e.g. ':') */
+			*(cpuspec-1) = ':';
+	}
+
 	process_gdb_output(concat_args(buf1, 0, TRUE), radix,
 			   sp ? sp->name : NULL, do_load_module_filter);
 }
@@ -6496,34 +6510,109 @@ process_gdb_output(char *gdb_request, un
 }
 
 /*
+ *  Get the type of an expression using gdb's "whatis" command.
+ *  The returned string is dynamically allocated, and it should
+ *  be passed to FREEBUF() when no longer needed.
+ *  Return NULL if the type cannot be determined.
+ */
+static char *
+expr_type_name(const char *expr)
+{
+	char buf[BUFSIZE], *p;
+
+	open_tmpfile();
+	sprintf(buf, "whatis %s", expr);
+	if (!gdb_pass_through(buf, fp, GNU_RETURN_ON_ERROR)) {
+		close_tmpfile();
+		return NULL;
+	}
+
+	rewind(pc->tmpfile);
+	while (fgets(buf, BUFSIZE, pc->tmpfile) && !STRNEQ(buf, "type = "))
+		;
+	p = feof(pc->tmpfile) ? NULL : buf + strlen("type = ");
+	close_tmpfile();
+
+	if (p) {
+		size_t len = strlen(clean_line(p));
+		return strcpy(GETBUF(len + 1), p);
+	}
+	return NULL;
+}
+
+/*
  *  Display the datatype of the per_cpu__xxx symbol and 
  *  the addresses of each its per-cpu instances.
  */
 static int
-display_per_cpu_info(struct syment *sp)
+display_per_cpu_info(struct syment *sp, int radix, char *cpuspec)
 {
+	ulong *cpus;
 	int c;
 	ulong addr;
 	char buf[BUFSIZE];
+	char leader[sizeof("&per_cpu(") + strlen(sp->name) +
+		    sizeof(", " STR(UINT_MAX) ")")];
+	char *typename;
+	int do_load_module_filter;
 
 	if (((kt->flags & (SMP|PER_CPU_OFF)) != (SMP|PER_CPU_OFF)) ||
 	    (!is_percpu_symbol(sp)) ||
 	    !((sp->type == 'd') || (sp->type == 'D') || (sp->type == 'V')))
 		return FALSE;
 
-	fprintf(fp, "PER-CPU DATA TYPE:\n  ");
-        sprintf(buf, "whatis %s", sp->name);
-        if (!gdb_pass_through(buf, pc->nullfp, GNU_RETURN_ON_ERROR))
-                fprintf(fp, "[undetermined type] %s;\n", sp->name);
-	else
-        	whatis_variable(sp);
+	if (cpuspec) {
+		cpus = get_cpumask_buf();
+		if (STREQ(cpuspec, "")) {
+			SET_BIT(cpus, CURRENT_CONTEXT()->processor);
+		} else {
+			make_cpumask(cpuspec, cpus, FAULT_ON_ERROR, NULL);
+		}
+	} else
+		cpus = NULL;
+
+	typename = expr_type_name(sp->name);
+
+	if (!cpus) {
+		fprintf(fp, "PER-CPU DATA TYPE:\n  ");
+		if (!typename)
+			fprintf(fp, "[undetermined type] %s;\n", sp->name);
+		else
+			whatis_variable(sp);
+
+		fprintf(fp, "PER-CPU ADDRESSES:\n");
+	}
+
+	do_load_module_filter =
+		module_symbol(sp->value, NULL, NULL, NULL, *gdb_output_radix);
 
-	fprintf(fp, "PER-CPU ADDRESSES:\n");
 	for (c = 0; c < kt->cpus; c++) {
+		if (cpus && !NUM_IN_BITMAP(cpus, c))
+			continue;
 		addr = sp->value + kt->__per_cpu_offset[c];
-		fprintf(fp, "  [%d]: %lx\n", c, addr);
+		if (!cpus)
+			fprintf(fp, "  [%d]: %lx\n", c, addr);
+		else if (typename) {
+			snprintf(buf, sizeof buf, "p *(%s*) 0x%lx",
+				 typename, addr);
+			sprintf(leader, "per_cpu(%s, %u)",
+				sp->name, c);
+			process_gdb_output(buf, radix, leader,
+					   do_load_module_filter);
+		} else {
+			snprintf(buf, sizeof buf, "p (void*) 0x%lx", addr);
+			sprintf(leader, "&per_cpu(%s, %u)",
+				sp->name, c);
+			process_gdb_output(buf, radix, leader,
+					   do_load_module_filter);
+		}
 	}
 
+	if (typename)
+		FREEBUF(typename);
+	if (cpus)
+		FREEBUF(cpus);
+
 	return TRUE;
 }
 
--- a/help.c
+++ b/help.c
@@ -1096,6 +1096,13 @@ char *help_p[] = {
 "  are two other built-in aliases, \"px\" and \"pd\", which force the command",
 "  output to be displayed in hexadecimal or decimal, without changing the",
 "  default mode. ",
+"",
+"  If expression is a per-cpu variable, you can specify the cpu list after",
+"  a colon (':'). The following formats are available:",
+"",
+"             :  CPU of the currently selected task.",
+"            :a  all CPUs.",
+"  :#[-#][,...]  CPU list(s), e.g. \"1,3,5\", \"1-3\", or \"1,3,5-7,10\".",
 "\nEXAMPLES",
 "  Print the contents of jiffies:\n",
 "    %s> p jiffies",
@@ -1144,6 +1151,13 @@ char *help_p[] = {
 "      swap_address = 0x0, ",
 "      segments = 0x0",
 "    }",
+"",
+"  Print the contents of the variable \"blk_cpu_iopoll\" on the current CPU:\n",
+"    %s> p blk_cpu_iopoll:",
+"    per_cpu(blk_cpu_iopoll, 2) = $1 = {",
+"      next = 0xffff88011e290100, ",
+"      prev = 0xffff88011e290100",
+"    }",
 NULL               
 };
 
