[Pdbg] [RFC] main: Implement 'dmesg' command

Joel Stanley joel at jms.id.au
Fri May 25 13:23:04 AEST 2018


This command fetches the linux kernel log from host RAM and displays it
on stdout.

Almost any powernv kernel will support this. However it requires a
skiboot change in order to find the location of the kernel log buffer.
On systems without that change this command will return a message saying
it is unsupported.

Signed-off-by: Joel Stanley <joel at jms.id.au>
---
The guts of the log parsing comes from benh's kmsg_dump.c.

The skiboot change has not been merged yet:

 http://patchwork.ozlabs.org/patch/919508/

More importantly, this doesn't currently work, as we cannot read the
memory region that log_buf points to. Jump to the end of this essay to
see the error.

__log_buf[] is a statically allocated buffer that the kernel uses for
it's messages. However, we refer to it using the pointer log_buf, as the
kernel may chose to reallocate the buffer.

kernel/printk/printk.c:

        static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);
        static char *log_buf = __log_buf;
        static u32 log_buf_len = __LOG_BUF_LEN;

        /* Return log buffer address */
        char *log_buf_addr_get(void)
        {
                return log_buf;
        }

        ...

	if (early) {
                new_log_buf =
                        memblock_virt_alloc(new_log_buf_len, LOG_ALIGN);
        } else {
                new_log_buf = memblock_virt_alloc_nopanic(new_log_buf_len,
                                                          LOG_ALIGN);
        }

The value gets retrieved via log_buf_addr_get in the powernv platform
code, and sent to skiboot via the opal_register_dump_region OPAL call.

arch/powerpc/platforms/powernv/opal.c

        addr = log_buf_addr_get();
        if (addr == NULL)
                return;
        ...

        rc = opal_register_dump_region(OPAL_DUMP_REGION_LOG_BUF,
                                       __pa(addr), size);

Skiboot sticks this in the debug descriptor:

core/dump-region.c

        static int64_t bmc_opal_register_dump_region(uint32_t id,
                                                     uint64_t addr, uint64_t size)
        {
                ...
                prlog(PR_INFO, "Registered log buf at 0x%016llx\n", addr);
                debug_descriptor.log_buf_phys = addr;

Looking at the host's kallsyms, we can see where __log_buf starts. If we
hardcode that as the address to read from in pdbg, we can get data out:

/proc/kallsyms:
        c000000001572a88 d log_buf
        c0000000018bde84 b __log_buf

Here's the address we actually get:

/sys/firmware/opal/msglog:
        DUMP: Registered log buf at 0x0000201cc69e0000

When we getmem:

	$ pdbg -p0 getmem 0x0000201cc69e0000 8
	p9_adu_getmem: Unable to read memory. ALTD_STATUS_REG = 0x0080000000000000

This is an invalid address error:

8     RWX_WCLRREG             FBC_ALTD_ADDRESS_ERROR: Invalid Adress Error: PB respond with Adress Errror cresp
---
 Makefile.am |   2 +-
 src/dmesg.c | 218 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/main.c  |   2 +
 3 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 src/dmesg.c

diff --git a/Makefile.am b/Makefile.am
index cf04b942d475..e51d01f77f02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -28,7 +28,7 @@ BUILT_SOURCES = $(DT) $(DT_headers)
 
 pdbg_SOURCES = \
 	src/main.c src/cfam.c src/scom.c src/reg.c src/mem.c src/thread.c \
-	src/ring.c src/htm.c src/progress.c src/options_ at ARCH@.c
+	src/ring.c src/htm.c src/progress.c src/dmesg.c src/options_ at ARCH@.c
 
 pdbg_LDADD = $(DT_objects) libpdbg.la libfdt.la \
 	-L.libs -lrt
diff --git a/src/dmesg.c b/src/dmesg.c
new file mode 100644
index 000000000000..89d925bb2de7
--- /dev/null
+++ b/src/dmesg.c
@@ -0,0 +1,218 @@
+/* Copyright 2018 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 	http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <byteswap.h>
+
+#include <operations.h>
+#include <target.h>
+#include <debug.h>
+
+#include "main.h"
+
+/* Linux kernel log buffer flags */
+enum log_flags {
+	LOG_NOCONS      = 1,    /* already flushed, do not print to console */
+        LOG_NEWLINE     = 2,    /* text ended with a newline */
+        LOG_PREFIX      = 4,    /* text started with a prefix */
+        LOG_CONT        = 8,    /* text is a fragment of a continuation line */
+};
+
+/* Linux kernel log buffer structure */
+struct log {
+        uint64_t ts_nsec;            /* timestamp in nanoseconds */
+        uint16_t len;                /* length of entire record */
+        uint16_t text_len;           /* length of text buffer */
+        uint16_t dict_len;           /* length of dictionary buffer */
+        uint8_t facility;            /* syslog facility */
+	union {
+		struct {
+			uint8_t flags:5; /* internal record flags */
+			uint8_t level:3; /* syslog level */
+		} normal;
+		struct {
+			uint8_t level:3; /* syslog level */
+			uint8_t flags:5; /* internal record flags */
+		} swapped;
+	} fl;
+};
+
+#define OPAL_DEBUG_DESCRIPTOR_ADDR	0x30000080
+
+/* OPAL debug descriptor (minus the traces) */
+struct debug_descriptor {
+	uint8_t		eye_catcher[8]; /* "OPALdbug" */
+#define DEBUG_DESC_VERSION      1
+	uint32_t	version;
+	uint8_t		console_log_levels;     /* high 4 bits in memory,
+						 * low 4 bits driver (e.g. uart). */
+	uint8_t		state_flags; /* various state flags - OPAL_BOOT_COMPLETE etc */ 
+	uint16_t	reserved2;
+	uint64_t	log_buf_phys;
+
+	/* Memory console */
+	uint64_t	memcons_phys;
+	uint32_t	memcons_tce;
+	uint32_t	memcons_obuf_tce;
+	uint32_t	memcons_ibuf_tce;
+} __packed;
+
+
+static size_t print_time(uint64_t ts)
+{
+	unsigned long rem_nsec;
+
+	rem_nsec = ts / 1000000000;
+
+	return printf("[%5lu.%06lu] ", (unsigned long)ts, rem_nsec / 1000);
+}
+
+static size_t print_prefix(const struct log *msg, unsigned int level)
+{
+	size_t len = 0;
+	unsigned int prefix = (msg->facility << 3) | level;
+
+	len += printf("<%u>", prefix);
+	len += print_time(msg->ts_nsec);
+	return len;
+}
+
+static int display_dmesg(struct pdbg_target *target, uint64_t log_addr, bool swap)
+{
+	for (;;) {
+		size_t sz;
+		struct log l;
+		unsigned int flags, level;
+		uint8_t buf[0x10000];
+		bool prefix = true;
+		bool newline = true;
+		uint8_t *text;
+		unsigned int text_size;
+		unsigned int prev = 0;
+
+		sz = adu_getmem(target, log_addr, (uint8_t *)&l, sizeof(struct log));
+		if (sz)
+			exit(0);
+
+		if (swap) {
+			l.ts_nsec = bswap_64(l.ts_nsec);
+			l.len = bswap_16(l.len);
+			l.text_len = bswap_16(l.text_len);
+			l.dict_len = bswap_16(l.dict_len);
+			flags = l.fl.swapped.flags;
+			level = l.fl.swapped.level;
+		} else {
+			flags = l.fl.normal.flags;
+			level = l.fl.normal.level;
+		}
+
+		if (l.len == 0)
+			break;
+
+		sz = adu_getmem(target, log_addr + sizeof(struct log), buf,
+				l.len - sizeof(struct log));
+		if (sz)
+			break;
+		log_addr += l.len;
+
+		if ((prev & LOG_CONT) && !(flags & LOG_PREFIX))
+			prefix = false;
+
+		if (flags & LOG_CONT) {
+			if ((prev & LOG_CONT) && !(prev & LOG_NEWLINE))
+				prefix = false;
+
+			if (!(flags & LOG_NEWLINE))
+				newline = false;
+		}
+
+		text = buf;
+		text_size = l.text_len;
+		do {
+			uint8_t *next = memchr(text, '\n', text_size);
+			size_t text_len;
+
+			if (next) {
+				text_len = next - text;
+				next++;
+				text_size -= next - text;
+			} else {
+				text_len = text_size;
+			}
+
+			if (prefix)
+				print_prefix(&l, level);
+			fwrite(text, text_len, 1, stdout);
+			if (next || newline)
+				printf("\n");
+			prefix = true;
+			text = next;
+		} while (text);
+	}
+	return 0;
+}
+
+int handle_dmesg(int optind, int argc, char *argv[])
+{
+	struct pdbg_target *target;
+	struct debug_descriptor dd;
+	uint64_t dd_addr;
+	uint64_t log_buf_phys;
+	int ret;
+
+	pdbg_for_each_class_target("adu", target) {
+		if (!target_selected(target->parent))
+			continue;
+		if (pdbg_target_probe(target) != PDBG_TARGET_ENABLED)
+			continue;
+		break;
+	};
+	assert(target);
+
+	ret = adu_getmem(target, OPAL_DEBUG_DESCRIPTOR_ADDR,
+			(uint8_t *)&dd_addr, sizeof(dd_addr));
+	if (ret < 0) {
+		PR_ERROR("unable to read memory\n");
+		exit(-1);
+	}
+	dd_addr = be64toh(dd_addr);
+	PR_DEBUG("dd_addr: %s 0x%016"PRIx64"\n", __func__, dd_addr);
+
+	ret = adu_getmem(target, dd_addr, (uint8_t *)&dd, sizeof(dd));
+	if (ret < 0) {
+		PR_ERROR("unable to read memory\n");
+		exit(-1);
+	}
+	log_buf_phys = be64toh(dd.log_buf_phys);
+	PR_DEBUG("log_buf_phys: 0x%016"PRIx64"\n", log_buf_phys);
+
+	if (log_buf_phys == 0) {
+		PR_ERROR("dmesg not registered with skiboot\n");
+		exit(-1);
+	}
+	ret = display_dmesg(target, log_buf_phys, true);
+	if (ret < 0) {
+		PR_ERROR("unable to display dmesg\n");
+		exit(-1);
+	}
+	return 0;
+}
diff --git a/src/main.c b/src/main.c
index 2200a01c5e73..731795f69bd3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -41,6 +41,7 @@
 #include "mem.h"
 #include "thread.h"
 #include "htm.h"
+#include "dmesg.h"
 #include "options.h"
 
 #define PR_ERROR(x, args...) \
@@ -122,6 +123,7 @@ static struct action actions[] = {
 	{ "threadstatus", "", "Print the status of a thread", &thread_status_print },
 	{ "sreset",  "", "Reset", &thread_sreset },
 	{ "regs",  "", "State", &thread_state },
+	{ "dmesg", "", "Display kernel log", &handle_dmesg },
 };
 
 
-- 
2.17.0



More information about the Pdbg mailing list