[RFC,1/2] libc: Add custom printf specifiers

Message ID 20171006053029.1118-1-oohall@gmail.com
State New
Headers show
Series
  • [RFC,1/2] libc: Add custom printf specifiers
Related show

Commit Message

Oliver Oct. 6, 2017, 5:30 a.m.
Add support for %p<custom> printf specifiers to our vsnprintf. This is
similar to the custom printf specifier support that exists in Linux for
dumping the contents of certain data structures. Hopefully this will
make writing better logging a bit easier all around.

Cc: matthew.brown.dev@gmail.com
Signed-off-by: Oliver O'Halloran <oohall@gmail.com>
---
This is based on something Matt wrote. I've reworked it heavily,
but it's still pretty similar overall.
---
 libc/include/stdio.h   | 10 +++++++
 libc/stdio/vsnprintf.c | 79 +++++++++++++++++++++++++++++++++++++++++++++-----
 skiboot.lds.S          |  6 ++++
 3 files changed, 87 insertions(+), 8 deletions(-)

Patch

diff --git a/libc/include/stdio.h b/libc/include/stdio.h
index 5dc4502b0a5d..9adf9586dfb9 100644
--- a/libc/include/stdio.h
+++ b/libc/include/stdio.h
@@ -69,4 +69,14 @@  int vsscanf(const char *str, const char *format, va_list);
 int getc(FILE *stream);
 int getchar(void);
 
+/* non-std io here */
+
+struct custom_format {
+	const char *specifier;
+	/* writes a formatted version of *value into the printf buffer */
+	int (*func) (char **buffer, size_t bufsize, const void *value);
+};
+
+#define DECLARE_PRINTFMT(name) \
+static const struct custom_format __used __section(".custom_printf") name ##_fmt
 #endif
diff --git a/libc/stdio/vsnprintf.c b/libc/stdio/vsnprintf.c
index 410a806cb388..c45f78c0c495 100644
--- a/libc/stdio/vsnprintf.c
+++ b/libc/stdio/vsnprintf.c
@@ -124,9 +124,43 @@  print_fill(char **buffer, size_t bufsize, char *sizec, unsigned long size,
 }
 
 
+extern struct custom_format __custom_printf_start;
+extern struct custom_format __custom_printf_end;
+
+/*
+ * Check if input string matches or is a substring of the
+ * custom specifiers.
+ *
+ * Return true is strings match / are substrings.
+ * If strings match exactly, the res pointer is set to the corresponding
+ * custom format specifier struct.
+ */
+static const struct custom_format *find_custom_fmt(const char *format)
+{
+	struct custom_format *fmt;
+
+	for (fmt = &__custom_printf_start; fmt < &__custom_printf_end; fmt++) {
+		const char *spec = fmt->specifier;
+		int i = 0;
+
+		/*
+		 * we don't know how long the specifier is, so do a bytewise
+		 * compare.
+		 */
+		while (format[i] == spec[i] && format[i] && spec[i])
+			i++;
+
+		if (spec[i] == '\0')
+			return fmt;
+	}
+
+	return NULL;
+}
+
 static int
 print_format(char **buffer, size_t bufsize, const char *format, void *var)
 {
+	const struct custom_format *custom_format;
 	char *start;
 	unsigned int i = 0, length_mod = sizeof(int);
 	unsigned long value = 0;
@@ -134,7 +168,6 @@  print_format(char **buffer, size_t bufsize, const char *format, void *var)
 	char *form, sizec[32];
 	char sign = ' ';
 	bool upper = false;
-
 	form  = (char *) format;
 	start = *buffer;
 
@@ -144,6 +177,13 @@  print_format(char **buffer, size_t bufsize, const char *format, void *var)
 		form++;
 	}
 
+	/* check for custom printfs */
+	custom_format = find_custom_fmt(format);
+	if (custom_format) {
+		custom_format->func(buffer, bufsize, var);
+		return (long int) (*buffer - start);
+	}
+
 	while ((*form != '\0') && ((*buffer - start) < bufsize)) {
 		switch(*form) {
 			case 'u':
@@ -239,7 +279,6 @@  print_format(char **buffer, size_t bufsize, const char *format, void *var)
 	return (long int) (*buffer - start);
 }
 
-
 /*
  * The vsnprintf function prints a formatted strings into a buffer.
  * BUG: buffer size checking does not fully work yet
@@ -247,6 +286,7 @@  print_format(char **buffer, size_t bufsize, const char *format, void *var)
 int
 vsnprintf(char *buffer, size_t bufsize, const char *format, va_list arg)
 {
+	const struct custom_format *fmt;
 	char *ptr, *bstart;
 
 	bstart = buffer;
@@ -266,20 +306,43 @@  vsnprintf(char *buffer, size_t bufsize, const char *format, va_list arg)
 	{
 		if(*ptr == '%') {
 			char formstr[20];
-			int i=0;
-			
+			int i = 0;
+
 			do {
 				formstr[i] = *ptr;
 				ptr++;
 				i++;
-			} while(!(*ptr == 'd' || *ptr == 'i' || *ptr == 'u' || *ptr == 'x' || *ptr == 'X'
-						|| *ptr == 'p' || *ptr == 'c' || *ptr == 's' || *ptr == '%'
-						|| *ptr == 'O' || *ptr == 'o' )); 
+			} while(!(*ptr == 'd' || *ptr == 'i' ||
+				  *ptr == 'u' || *ptr == 'x' ||
+				  *ptr == 'X' || *ptr == 'p' ||
+				  *ptr == 's' || *ptr == '%' ||
+				  *ptr == 'O' || *ptr == 'o'));
+
+			/* Add last char to buffer*/
 			formstr[i++] = *ptr;
 			formstr[i] = '\0';
-			if(*ptr == '%') {
+
+			/*
+			 * if we terminated on a p then check if the next
+			 * few characters match one of our custom formats
+			 */
+			if (*ptr == 'p')
+				fmt = find_custom_fmt(ptr + 1);
+			else
+				fmt = NULL;
+
+			if (*ptr == '%') {
 				*buffer++ = '%';
+			} else if (fmt)  {
+				fmt->func(&buffer,
+					bufsize - (buffer - bstart),
+					va_arg(arg, void *));
+				ptr += strlen(fmt->specifier);
 			} else {
+				/*
+				 * This changes the format specifier into the
+				 * actual string.
+				 */
 				print_format(&buffer,
 					bufsize - (buffer - bstart),
 					formstr, va_arg(arg, void *));
diff --git a/skiboot.lds.S b/skiboot.lds.S
index 7f71d5cd1b40..ef287c7870e4 100644
--- a/skiboot.lds.S
+++ b/skiboot.lds.S
@@ -115,6 +115,12 @@  SECTIONS
 		__platforms_end = .;
 	}
 
+	.custom_printf : {
+		__custom_printf_start = .;
+		KEEP(*(.custom_printf))
+		__custom_printf_end = .;
+	}
+
 	/* Do I need to keep these ? */
 	.dynsym : { *(.dynsym)	}
 	.dynstr : { *(.dynstr)	}