diff --git a/Makefile.am b/Makefile.am index bb1ff383..10a314d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -21,7 +21,7 @@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = include src tests examples +SUBDIRS = include src tests tools examples if BUILD_PYTHON_EXT SUBDIRS += python endif diff --git a/NEWS b/NEWS index 4aea7305..0f6b8332 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ next * Parse QEMU CPU state ELF notes. * Use kernel page tables when initializing X86-64 Linux with PTI from CR3 register value. + * Include the kdumpid utility. * Fix direct mapping if LDT PTI remapping is used in Linux on X86-64. * Minor cache improvements and a NULL-pointer dereference fix. * Fix test suite for 32-bit architectures. diff --git a/README.md b/README.md index 90ca1733..d717dd31 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,12 @@ To compile this package, you'll need the following: * [GCC](http://gcc.gnu.org/). The source uses a few construct specific to GCC (such as variable attributes). Porting should be easy, though. +If you want to build kdumpid, you'll also need: + +* [BFD](http://www.gnu.org/software/binutils/). Any version with + disassemblers for x86, ppc and s390 will do. This usually comes with + the distro packaged as binutils-devel or similar. + To create documentation files, you'll need: * [Doxygen](http://www.doxygen.org/). Usually packaged as doxygen. diff --git a/configure.ac b/configure.ac index 93ebb39d..691a50eb 100644 --- a/configure.ac +++ b/configure.ac @@ -24,6 +24,10 @@ m4_define([pkg_micro_version], [4]) m4_define([pkg_version], [pkg_major_version.pkg_minor_version.pkg_micro_version]) +dnl FIXME: kdumpid has a different versioning scheme +m4_define([kdumpid_major_version], [1]) +m4_define([kdumpid_minor_version], [7]) + AC_INIT([libkdumpfile],[pkg_version],[petr@tesarici.cz]) AC_CONFIG_SRCDIR([src/kdumpfile/diskdump.c]) @@ -104,6 +108,11 @@ AS_IF([test "x$enable_debug" = xyes], dnl check for Python kdump_PYTHON([2.7.0]) +dnl check whether to build optional tools +AC_SUBST(KDUMPID_VER_MAJOR, kdumpid_major_version) +AC_SUBST(KDUMPID_VER_MINOR, kdumpid_minor_version) +KDUMP_TOOL_KDUMPID + AC_CONFIG_FILES([ Makefile examples/Makefile @@ -114,6 +123,8 @@ AC_CONFIG_FILES([ src/kdumpfile/Makefile python/Makefile tests/Makefile + tools/Makefile + tools/kdumpid/Makefile libaddrxlat.pc libkdumpfile.pc include/libkdumpfile/kdumpfile.h diff --git a/m4/tools.m4 b/m4/tools.m4 new file mode 100644 index 00000000..5748b7bb --- /dev/null +++ b/m4/tools.m4 @@ -0,0 +1,99 @@ +# KDUMP_TRY_LINK(LIBRARIES) +# --------------------------------------------------------- +# Try to link the existing test source file with additional +# libraries. +# Set kdump_res to "yes" on success, else set it to "no". +# Standard error output is saved to conftest.linkerr. +AC_DEFUN([KDUMP_TRY_LINK],[dnl +kdump_save_LIBS="$LIBS" +kdump_save_ac_link="$ac_link" +LIBS="$1 $LIBS" +ac_link="$ac_link 2>conftest.linkerr" +AC_LINK_IFELSE([], kdump_res=yes, [kdump_res=no + $2]) +LIBS="$kdump_save_LIBS" +ac_link="$kdump_save_ac_link" +])# KDUMP_TRY_LINK + +AC_DEFUN([KDUMP_REPORT_LINKERR],[dnl +AS_ECHO("$as_me:$LINENO: all linker errors") >&AS_MESSAGE_LOG_FD +cat conftest.linkerr >&AS_MESSAGE_LOG_FD +])# KDUMP_REPORT_LINKERR + +AC_DEFUN([KDUMP_TRY_LINK_UNDEF],[dnl +kdump_save_LDFLAGS="$LDFLAGS" +LDFLAGS="-z undefs $2 $LDFLAGS" +KDUMP_TRY_LINK($1, KDUMP_REPORT_LINKERR) +LDFLAGS="$kdump_save_LDFLAGS" +])# KDUMP_TRY_LINK_UNDEF + +AC_DEFUN([KDUMP_DIS_ASM_CHECK_UNDEF],[dnl +AC_REQUIRE([AC_PROG_EGREP])dnl +AC_MSG_CHECKING([whether disassembler requires $1]) +KDUMP_TRY_LINK($DIS_ASM_LIBS) +AS_ECHO("$as_me:$LINENO: matching linker errors") >&AS_MESSAGE_LOG_FD +AS_IF([$EGREP "@<:@^A-Za-z0-9_@:>@($2)" conftest.linkerr >&AS_MESSAGE_LOG_FD], + [AC_MSG_RESULT(yes) + DIS_ASM_LIBS="$DIS_ASM_LIBS $1" + KDUMP_TRY_LINK_UNDEF($DIS_ASM_LIBS) + AS_IF([test yes != "$kdump_res"], + [AC_MSG_FAILURE([Link fails with $1])])], + [AC_MSG_RESULT(no)])dnl +])# KDUMP_DIS_ASM_CHECK_UNDEF + +AC_DEFUN([KDUMP_DIS_ASM_LIBS],[[dnl determine disassembler libraries +DIS_ASM_LIBS=-lopcodes +AC_LANG_CONFTEST([AC_LANG_PROGRAM( + [#include ], + [disassembler(bfd_arch_i386, FALSE, bfd_mach_x86_64, NULL);])]) +dnl ignore undefined symbols from missing linker dependencies +AC_MSG_CHECKING([for disassembler in $DIS_ASM_LIBS]) +KDUMP_TRY_LINK_UNDEF($DIS_ASM_LIBS, [[-Wl,--require-defined=disassembler]]) +AC_MSG_RESULT($kdump_res) +AS_IF([test yes = "$kdump_res"], [dnl + KDUMP_DIS_ASM_CHECK_UNDEF(-lbfd, bfd_) + KDUMP_DIS_ASM_CHECK_UNDEF(-lsframe, sframe_) + KDUMP_DIS_ASM_CHECK_UNDEF(-liberty, htab_create|splay_tree_new) + KDUMP_DIS_ASM_CHECK_UNDEF(-lz, inflate) + KDUMP_DIS_ASM_CHECK_UNDEF(-lzstd, ZSTD_) + KDUMP_DIS_ASM_CHECK_UNDEF(-ldl, dlopen) + AS_IF([test yes != "$kdump_res"], + [KDUMP_REPORT_LINKERR] + [AC_MSG_FAILURE([Tried everything, still cannot link disassembler.])]) + AC_SUBST(DIS_ASM_LIBS) +], false)dnl +]])# KDUMP_DIS_ASM_LIBS + +AC_DEFUN([KDUMP_DIS_ASM],[dnl determine disassembler options +AC_CHECK_HEADERS(dis-asm.h, [], + [AC_MSG_ERROR([Disassembler headers not found])]) +AC_MSG_CHECKING([whether disassembler supports syntax highlighting]) +AC_COMPILE_IFELSE([AC_LANG_SOURCE([ +#include +void fn(struct disassemble_info *info, void *stream, + fprintf_ftype fprintf_func, fprintf_styled_ftype fprintf_styled_func) +{ + init_disassemble_info(info, stream, fprintf_func, fprintf_styled_func); +} +])], + [dnl +AC_MSG_RESULT(yes) +AC_DEFINE(DIS_ASM_STYLED_PRINTF, [1], + [Define if init_disassemble_info() has a printf_styled_func parameter])], + [AC_MSG_RESULT(no)]) +KDUMP_DIS_ASM_LIBS +])# KDUMP_DIS_ASM + +AC_DEFUN([KDUMP_TOOL_KDUMPID],[dnl enable/disable kdumpid build +AC_ARG_ENABLE(kdumpid, + [AS_HELP_STRING(--disable-kdumpid, + [do not build kdumpid])], + [], + [enable_kdumpid=yes]) +AS_IF([test no != "$enable_kdumpid"], + [AS_IF(KDUMP_DIS_ASM, [], + [AC_MSG_FAILURE( + [disassembler test failed (--disable-kdumpid to disable)])] + )]) +AM_CONDITIONAL(BUILD_KDUMPID, [test yes = "$enable_kdumpid"]) +])# KDUMP_TOOL_KDUMPID diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 00000000..0425a79e --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,26 @@ +## Process this file with automake to create Makefile.in +## Configure input file for libkdumpfile. +## +## Copyright (C) 2024 Petr Tesarik +## +## This file is part of libkdumpfile. +## +## This file is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## libkdumpfile is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +SUBDIRS = + +if BUILD_KDUMPID +SUBDIRS += kdumpid +endif diff --git a/tools/kdumpid/.gitignore b/tools/kdumpid/.gitignore new file mode 100644 index 00000000..3295c678 --- /dev/null +++ b/tools/kdumpid/.gitignore @@ -0,0 +1,2 @@ +# Resulting binaries +kdumpid diff --git a/tools/kdumpid/Makefile.am b/tools/kdumpid/Makefile.am new file mode 100644 index 00000000..11960266 --- /dev/null +++ b/tools/kdumpid/Makefile.am @@ -0,0 +1,45 @@ +## Process this file with automake to create Makefile.in +## Configure input file for libkdumpfile. +## +## Copyright (C) 2024 Petr Tesarik +## +## This file is part of libkdumpfile. +## +## This file is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## libkdumpfile is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +AM_CPPFLAGS = -I$(top_builddir)/include \ + -DVER_MAJOR=$(KDUMPID_VER_MAJOR) \ + -DVER_MINOR=$(KDUMPID_VER_MINOR) + +LIBS = \ + $(top_builddir)/src/kdumpfile/libkdumpfile.la \ + $(DIS_ASM_LIBS) + +kdumpid_SOURCES = \ + main.c \ + util.c \ + search.c \ + ppc.c \ + ppc64.c \ + s390.c \ + x86.c + +noinst_HEADERS = \ + kdumpid.h \ + endian.h + +bin_PROGRAMS = kdumpid + +dist_man_MANS = kdumpid.1 diff --git a/tools/kdumpid/endian.h b/tools/kdumpid/endian.h new file mode 100644 index 00000000..fd9b7d16 --- /dev/null +++ b/tools/kdumpid/endian.h @@ -0,0 +1,44 @@ +#ifndef __KDUMP_ENDIAN_H +#define __KDUMP_ENDIAN_H + +#include + +/* Older glibc didn't have the byteorder macros */ +#ifndef be16toh + +#include + +# if __BYTE_ORDER == __LITTLE_ENDIAN +# define htobe16(x) bswap_16(x) +# define htole16(x) (x) +# define be16toh(x) bswap_16(x) +# define le16toh(x) (x) + +# define htobe32(x) bswap_32(x) +# define htole32(x) (x) +# define be32toh(x) bswap_32(x) +# define le32toh(x) (x) + +# define htobe64(x) bswap_64(x) +# define htole64(x) (x) +# define be64toh(x) bswap_64(x) +# define le64toh(x) (x) +# else +# define htobe16(x) (x) +# define htole16(x) bswap_16(x) +# define be16toh(x) (x) +# define le16toh(x) bswap_16(x) + +# define htobe32(x) (x) +# define htole32(x) bswap_32(x) +# define be32toh(x) (x) +# define le32toh(x) bswap_32(x) + +# define htobe64(x) (x) +# define htole64(x) bswap_64(x) +# define be64toh(x) (x) +# define le64toh(x) bswap_64(x) +# endif +#endif + +#endif /* __KDUMP_ENDIAN_H */ diff --git a/tools/kdumpid/kdumpid.1 b/tools/kdumpid/kdumpid.1 new file mode 100644 index 00000000..11a6f81c --- /dev/null +++ b/tools/kdumpid/kdumpid.1 @@ -0,0 +1,55 @@ +.TH KDUMPID 1 "4 Nov 2011" +.SH NAME +KdumpID \- A tool to identify kernel memory dumps +.SH SYNOPSIS +.B kdumpid +.I [-v] +.SH "DESCRIPTION" +.B kdumpid +provides a fast and reliable method to find out the most +important information about an unknown kernel crash dump, +such as the architecture and kernel release. Think of it +as a kind of "file" utility for kernel dumps. +.LP +At present, +.B kdumpid +can read: +.LP +\- LKCD files +.br +\- DISKDUMP/KDUMP files +.br +\- ELF dumps (including support for both Xen Dom0 and DomU). +.LP +The following architectures are fully supported: +.LP +\- x86 +.br +\- x86-64 +.br +\- ppc +.br +\- ppc64 +.br +\- s390 +.br +\- s390x. +.LP +Other architectures may produce some output if the information +can be found in the file header. +.SH "OPERATION" +By default, +.B kdumpid +will print the kernel dump's format, architecture and version. +.SH "OPTIONS" +.TP +\fB\-v\fR +Try to extract and print additional information from +the memory dump, such as: the machine type, the full +kernel banner string and the kernel configuration flavor. +.SH "SEE ALSO" +.BR crash (8), +.BR makedumpfile (8), +.BR kdump (7). +.SH AUTHOR +KdumpID was written by Petr Tesarik. diff --git a/tools/kdumpid/kdumpid.h b/tools/kdumpid/kdumpid.h new file mode 100644 index 00000000..5bba1a66 --- /dev/null +++ b/tools/kdumpid/kdumpid.h @@ -0,0 +1,81 @@ +#ifndef __KDUMPID_H +#define __KDUMPID_H + +#include "config.h" + +#include +#include +#include + +#include "endian.h" + +#define INVALID_ADDR ((uint64_t)-1ULL) + +struct dump_desc; + +struct dump_desc { + const char *name; /* file name */ + long flags; /* see DIF_XXX below */ + int fd; /* dump file descriptor */ + kdump_ctx_t *ctx; /* kdumpfile context */ + + void *page; /* page data buffer */ + kdump_num_t page_size; /* target page size */ + unsigned long max_pfn; /* max PFN for read_page */ + + const char *format; /* format name */ + + const char *arch; /* architecture (if known) */ + kdump_num_t endian; /* target byte order */ + uint64_t start_addr; /* kernel start address */ + + char machine[66]; /* arch name (utsname machine) */ + char ver[66]; /* version (utsname release) */ + char banner[256]; /* Linux banner */ + + char *cfg; /* kernel configuration */ + size_t cfglen; + + kdump_num_t xen_type; /* Xen dump type (or kdump_xen_none) */ + uint64_t xen_start_info; /* address of Xen start info */ + + void *priv; +}; + +/* Kdumpid flags */ +#define DIF_VERBOSE 1 +#define DIF_FORCE 2 +#define DIF_START_FOUND 8 + +/* Arch-specific helpers */ +int looks_like_kcode_ppc(struct dump_desc *dd, uint64_t addr); +int looks_like_kcode_ppc64(struct dump_desc *dd, uint64_t addr); +int looks_like_kcode_s390(struct dump_desc *dd, uint64_t addr); +int looks_like_kcode_x86(struct dump_desc *dd, uint64_t addr); + +/* provide our own definition of new_utsname */ +struct new_utsname { + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; + char domainname[65]; +}; + +/* utils */ + +int get_version_from_banner(struct dump_desc *dd); +int need_explore(struct dump_desc *dd); + +int read_page(struct dump_desc *dd, unsigned long pfn); +size_t dump_cpin(struct dump_desc *dd, void *buf, uint64_t paddr, size_t len); + +int uncompress_config(struct dump_desc *dd, void *zcfg, size_t zsize); +uint64_t dump_search_range(struct dump_desc *dd, + uint64_t start, uint64_t end, + const unsigned char *needle, size_t len); + +int explore_raw_data(struct dump_desc *dd); + +#endif /* __KDUMPID_H */ diff --git a/tools/kdumpid/main.c b/tools/kdumpid/main.c new file mode 100644 index 00000000..0a7c8f19 --- /dev/null +++ b/tools/kdumpid/main.c @@ -0,0 +1,268 @@ +/* + * main.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kdumpid.h" + +static void +print_xen_info(kdump_ctx_t *ctx) +{ + kdump_attr_t attr; + kdump_status status; + + fputs("Xen: ", stdout); + status = kdump_get_attr(ctx, "xen.version.major", &attr); + if (status == KDUMP_OK) + printf("%ld.", attr.val.number); + else + fputs("?.", stdout); + + status = kdump_get_attr(ctx, "xen.version.minor", &attr); + if (status == KDUMP_OK) + printf("%ld", attr.val.number); + else + fputs("?", stdout); + + status = kdump_get_attr(ctx, "xen.version.extra", &attr); + if (status == KDUMP_OK) + puts(attr.val.string); + else + putchar('\n'); +} + +static void +version(FILE *out, const char *progname) +{ + fprintf(out, "%s version %d.%d\n", + basename(progname), VER_MAJOR, VER_MINOR); +} + +static void +help(FILE *out, const char *progname) +{ + fprintf(out, "Usage: %s [-f] [-v] \n", + basename(progname)); +} + +#define SHORTOPTS "fhv" + +static void +print_verbose(struct dump_desc *dd) +{ + if (dd->machine[0]) + printf("Machine: %s\n", dd->machine); + if (*dd->banner) + printf("Banner: %s\n", dd->banner); + + if (dd->cfg) { + char *local = strstr(dd->cfg, "CONFIG_LOCALVERSION="); + if (local) { + char c, *end = strchr(local, '\n'); + if (end) { + c = *end; + *end = 0; + } + printf("Cfg release: %s\n", local + 20); + if (end) + *end = c; + } + } +} + +int +main(int argc, char **argv) +{ + static const struct option opts[] = { + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 256 }, + {0, 0, 0, 0} + }; + struct dump_desc dd; + const char *str; + kdump_status status; + int c, opt; + + /* Initialize dd */ + memset(&dd, 0, sizeof dd); + + while ( (c = getopt_long(argc, argv, SHORTOPTS, opts, &opt)) != -1 ) + switch(c) { + case 'f': + dd.flags |= DIF_FORCE; + break; + case 'h': + help(stdout, argv[0]); + return 0; + case 'v': + dd.flags |= DIF_VERBOSE; + break; + case 256: + version(stdout, argv[0]); + return 0; + } + + if (argc - optind != 1) { + help(stderr, argv[0]); + return 1; + } + dd.name = argv[optind]; + + if ((dd.fd = open(dd.name, O_RDONLY)) < 0) { + perror(dd.name); + return 2; + } + + dd.ctx = kdump_new(); + if (!dd.ctx) { + perror("Cannot allocate dump file context"); + close(dd.fd); + return 2; + } + + status = kdump_set_number_attr(dd.ctx, KDUMP_ATTR_ZERO_EXCLUDED, 1); + if (status != KDUMP_OK) + fprintf(stderr, "WARNING: Excluded pages are not zeroed: %s\n", + kdump_get_err(dd.ctx)); + + status = kdump_set_number_attr(dd.ctx, KDUMP_ATTR_FILE_FD, dd.fd); + if (status != KDUMP_OK) { + fprintf(stderr, "File initialization failed: %s\n", + kdump_get_err(dd.ctx)); + close(dd.fd); + return 2; + } + + if (dd.flags & DIF_FORCE) { + status = kdump_get_number_attr(dd.ctx, "max_pfn", + &dd.max_pfn); + if (status != KDUMP_OK) { + fprintf(stderr, "Cannot get max PFN: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + } + + kdump_set_string_attr(dd.ctx, KDUMP_ATTR_OSTYPE, "linux"); + + status = kdump_get_number_attr(dd.ctx, KDUMP_ATTR_PAGE_SIZE, + &dd.page_size); + if (status != KDUMP_OK) { + fprintf(stderr, "Cannot get page size: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + status = kdump_get_string_attr(dd.ctx, "linux.uts.release", &str); + if (status == KDUMP_OK) + strcpy(dd.ver, str); + else if (status == KDUMP_ERR_NODATA) + dd.ver[0] = '\0'; + else { + fprintf(stderr, "Cannot get UTS release: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + status = kdump_get_string_attr(dd.ctx, "linux.uts.machine", &str); + if (status == KDUMP_OK) + strcpy(dd.machine, str); + else if (status == KDUMP_ERR_NODATA) + dd.machine[0] = '\0'; + else { + fprintf(stderr, "Cannot get UTS machine: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + status = kdump_get_string_attr(dd.ctx, KDUMP_ATTR_ARCH_NAME, &dd.arch); + if (status == KDUMP_ERR_NODATA) + dd.arch = NULL; + else if (status != KDUMP_OK) { + fprintf(stderr, "Cannot get architecture name: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + status = kdump_get_number_attr(dd.ctx, "arch.byte_order", &dd.endian); + if (status == KDUMP_ERR_NODATA) + dd.endian = (kdump_num_t)-1; + else if (status != KDUMP_OK) { + fprintf(stderr, "Cannot get architecture byte order: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + status = kdump_get_string_attr(dd.ctx, KDUMP_ATTR_FILE_FORMAT, + &dd.format); + if (status == KDUMP_ERR_NODATA) + dd.format = NULL; + else if (status != KDUMP_OK) { + fprintf(stderr, "Cannot get architecture name: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + status = kdump_get_number_attr(dd.ctx, KDUMP_ATTR_XEN_TYPE, + &dd.xen_type); + if (status == KDUMP_ERR_NODATA) + dd.xen_type = KDUMP_XEN_NONE; + else if (status != KDUMP_OK) { + fprintf(stderr, "Cannot determine Xen type: %s\n", + kdump_get_err(dd.ctx)); + kdump_free(dd.ctx); + return 2; + } + + if (need_explore(&dd)) + explore_raw_data(&dd); + + if (!*dd.ver) + get_version_from_banner(&dd); + + printf("Format: %s%s\n", dd.format ?: "", + dd.xen_type != KDUMP_XEN_NONE ? ", Xen" : ""); + printf("Arch: %s\n", dd.arch); + printf("Version: %s\n", dd.ver); + if (dd.xen_type != KDUMP_XEN_NONE) + print_xen_info(dd.ctx); + + if (dd.flags & DIF_VERBOSE) + print_verbose(&dd); + + /* Intentionally ignore errors on close */ + kdump_free(dd.ctx); + close(dd.fd); + + return 0; +} diff --git a/tools/kdumpid/ppc.c b/tools/kdumpid/ppc.c new file mode 100644 index 00000000..1c63b182 --- /dev/null +++ b/tools/kdumpid/ppc.c @@ -0,0 +1,161 @@ +/* + * ppc.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include + +#include "kdumpid.h" + +#define MAX_INSN_LEN 100 + +struct disas_state { + unsigned long flags; +}; + +#define SRR0_SET 1 +#define SRR1_SET 2 + +struct disas_priv { + char *iptr; + struct disas_state state; + + char insn[MAX_INSN_LEN]; +}; + +static const char sep[] = ", \t\r\n"; +#define wsep (sep+1) + +static disassembler_ftype print_insn; + +static void +append_insn(void *data, const char *fmt, va_list va) +{ + struct disas_priv *priv = data; + size_t remain; + int len; + + remain = priv->insn + sizeof(priv->insn) - priv->iptr; + len = vsnprintf(priv->iptr, remain, fmt, va); + if (len > 0) + priv->iptr += len; + +} + +static int +disas_fn(void *data, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#ifdef DIS_ASM_STYLED_PRINTF + +static int +disas_styled_fn(void *data, enum disassembler_style style, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#endif /* DIS_ASM_STYLED_PRINTF */ + +static void error_func(int status, bfd_vma memaddr, + struct disassemble_info *dinfo) +{ + /* intentionally empty */ +} + +static int +disas_at(struct dump_desc *dd, struct disassemble_info *info, unsigned pc) +{ + struct disas_priv *priv = info->stream; + char *toksave; + char *insn; + int count; + + do { + priv->iptr = priv->insn; + count = print_insn(info->buffer_vma + pc, info); + if (count < 0) + break; + pc += count; + + insn = strtok_r(priv->insn, wsep, &toksave); + + /* for historical reasons, ppc starts with 3 NOPs */ + if (pc <= 3 * 4 && strcmp(insn, "nop")) + break; + + /* MMU is switched on with an rfi */ + if (priv->state.flags & SRR0_SET && + priv->state.flags & SRR1_SET && + !strcmp(insn, "rfi")) + return 1; + + if (!strcmp(insn, "mtsrr0")) + priv->state.flags |= SRR0_SET; + if (!strcmp(insn, "mtsrr1")) + priv->state.flags |= SRR1_SET; + + /* invalid instruction? */ + if (!strcmp(insn, ".long")) + break; + } while (count > 0); + + return 0; +} + +int +looks_like_kcode_ppc(struct dump_desc *dd, uint64_t addr) +{ + struct disassemble_info info; + struct disas_priv priv; + + /* check ppc startup code */ + if (read_page(dd, addr / dd->page_size)) + return -1; + + memset(&priv, 0, sizeof priv); +#ifdef DIS_ASM_STYLED_PRINTF + init_disassemble_info(&info, &priv, disas_fn, disas_styled_fn); +#else + init_disassemble_info(&info, &priv, disas_fn); +#endif + info.memory_error_func = error_func; + info.buffer = dd->page; + info.buffer_vma = addr; + info.buffer_length = dd->page_size; + info.arch = bfd_arch_powerpc; + info.mach = bfd_mach_ppc; + disassemble_init_for_target(&info); + print_insn = disassembler(bfd_arch_powerpc, + dd->endian != KDUMP_LITTLE_ENDIAN, + bfd_mach_ppc, NULL); + if (!print_insn) + return 0; + return disas_at(dd, &info, 0); +} diff --git a/tools/kdumpid/ppc64.c b/tools/kdumpid/ppc64.c new file mode 100644 index 00000000..11379f8e --- /dev/null +++ b/tools/kdumpid/ppc64.c @@ -0,0 +1,177 @@ +/* + * ppc64.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include + +#include "kdumpid.h" + +#define MAX_INSN_LEN 100 + +struct disas_state { + unsigned long flags; +}; + +#define SRR0_SET 1 +#define SRR1_SET 2 + +struct disas_priv { + char *iptr; + struct disas_state state; + + char insn[MAX_INSN_LEN]; +}; + +static const char sep[] = ", \t\r\n"; +#define wsep (sep+1) + +static disassembler_ftype print_insn; + +static void +append_insn(void *data, const char *fmt, va_list va) +{ + struct disas_priv *priv = data; + size_t remain; + int len; + + remain = priv->insn + sizeof(priv->insn) - priv->iptr; + len = vsnprintf(priv->iptr, remain, fmt, va); + if (len > 0) + priv->iptr += len; +} + +static int +disas_fn(void *data, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#ifdef DIS_ASM_STYLED_PRINTF + +static int +disas_styled_fn(void *data, enum disassembler_style style, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#endif /* DIS_ASM_STYLED_PRINTF */ + +static void +print_address(bfd_vma addr, struct disassemble_info *info) +{ + struct disas_priv *priv = info->stream; + size_t remain = priv->insn + sizeof(priv->insn) - priv->iptr; + int len = snprintf(priv->iptr, remain, + "0x%llx", (unsigned long long) addr); + if (len > 0) + priv->iptr += len; +} + +static void error_func(int status, bfd_vma memaddr, + struct disassemble_info *dinfo) +{ + /* intentionally empty */ +} + +static int +disas_at(struct dump_desc *dd, struct disassemble_info *info, unsigned pc) +{ + struct disas_priv *priv = info->stream; + char *toksave; + char *insn; + int count; + + do { + priv->iptr = priv->insn; + count = print_insn(info->buffer_vma + pc, info); + if (count < 0) + break; + pc += count; + + insn = strtok_r(priv->insn, wsep, &toksave); + + /* ppc64 starts with a jump instruction, but it + * may be NOPped out at runtime */ + if (pc == 4 && strcmp(insn, "b") && strcmp(insn, "nop")) + return 0; + + /* The next instruction should be a trap */ + if (pc == 8 && strcmp(insn, "trap")) + return 0; + + /* MSR can be modiied only in supervisor mode */ + if (!strcmp(insn, "mtmsrd")) + return 1; + + /* Alternatively, a good rfid will also serve */ + if (priv->state.flags & SRR0_SET && + priv->state.flags & SRR1_SET && + !strcmp(insn, "rfid")) + return 1; + + if (!strcmp(insn, "mtsrr0")) + priv->state.flags |= SRR0_SET; + if (!strcmp(insn, "mtsrr1")) + priv->state.flags |= SRR1_SET; + } while (count > 0); + + return 0; +} + +int +looks_like_kcode_ppc64(struct dump_desc *dd, uint64_t addr) +{ + struct disassemble_info info; + struct disas_priv priv; + + /* check ppc64 startup code */ + if (read_page(dd, addr / dd->page_size)) + return -1; + + memset(&priv, 0, sizeof priv); +#ifdef DIS_ASM_STYLED_PRINTF + init_disassemble_info(&info, &priv, disas_fn, disas_styled_fn); +#else + init_disassemble_info(&info, &priv, disas_fn); +#endif + info.print_address_func = print_address; + info.memory_error_func = error_func; + info.buffer = dd->page; + info.buffer_vma = addr; + info.buffer_length = dd->page_size; + info.arch = bfd_arch_powerpc; + info.mach = bfd_mach_ppc64; + disassemble_init_for_target(&info); + print_insn = disassembler(bfd_arch_powerpc, + dd->endian != KDUMP_LITTLE_ENDIAN, + bfd_mach_ppc64, NULL); + if (!print_insn) + return 0; + return disas_at(dd, &info, 0); +} diff --git a/tools/kdumpid/s390.c b/tools/kdumpid/s390.c new file mode 100644 index 00000000..0879cb99 --- /dev/null +++ b/tools/kdumpid/s390.c @@ -0,0 +1,170 @@ +/* + * s390.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include + +#include "kdumpid.h" + +#define DEFAULT_ZIPL_OFFSET 0x08000 +#define DEFAULT_LOAD_ADDR 0x10000 + +/* Minimum length of correctly decoded instructions */ +#define MIN_STARTUP_SIZE 0x40 + +#define MAX_INSN_LEN 100 + +struct disas_state { + unsigned long flags; +}; + +#define SAM64_SEEN 1 + +struct disas_priv { + char *iptr; + struct disas_state state; + + char insn[MAX_INSN_LEN]; +}; + +static const char sep[] = ", \t\r\n"; +#define wsep (sep+1) + +static disassembler_ftype print_insn; + +static void +append_insn(void *data, const char *fmt, va_list va) +{ + struct disas_priv *priv = data; + size_t remain; + int len; + + remain = priv->insn + sizeof(priv->insn) - priv->iptr; + len = vsnprintf(priv->iptr, remain, fmt, va); + if (len > 0) + priv->iptr += len; +} + +static int +disas_fn(void *data, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#ifdef DIS_ASM_STYLED_PRINTF + +static int +disas_styled_fn(void *data, enum disassembler_style style, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#endif /* DIS_ASM_STYLED_PRINTF */ + +static void error_func(int status, bfd_vma memaddr, + struct disassemble_info *dinfo) +{ + /* intentionally empty */ +} + +static int +disas_at(struct dump_desc *dd, struct disassemble_info *info, unsigned pc) +{ + struct disas_priv *priv = info->stream; + char *toksave; + char *insn; + int count; + + do { + priv->iptr = priv->insn; + count = print_insn(info->buffer_vma + pc, info); + if (count < 0) + break; + pc += count; + + insn = strtok_r(priv->insn, wsep, &toksave); + + /* s390 setup code always starts with a basr instruction */ + if (pc == 0 && strcmp(insn, "basr")) + break; + + /* Recognize z/Architecture from ESA/390 */ + if (!strcmp(insn, "sam64")) + priv->state.flags |= SAM64_SEEN; + + /* invalid instruction? */ + if (!strcmp(insn, ".long")) + break; + } while (count > 0); + + return (pc >= MIN_STARTUP_SIZE); +} + +int +looks_like_kcode_s390(struct dump_desc *dd, uint64_t addr) +{ + struct disassemble_info info; + struct disas_priv priv; + int ret = 0; + + /* check zIPL signature */ + if (read_page(dd, (addr + DEFAULT_ZIPL_OFFSET) / dd->page_size)) + return -1; + + if (!memcmp(dd->page, "zIPL", 4)) + ret |= 1; + + /* check s390 startup code */ + if (read_page(dd, (addr + DEFAULT_LOAD_ADDR) / dd->page_size)) + return -1; + + memset(&priv, 0, sizeof priv); +#ifdef DIS_ASM_STYLED_PRINTF + init_disassemble_info(&info, &priv, disas_fn, disas_styled_fn); +#else + init_disassemble_info(&info, &priv, disas_fn); +#endif + info.memory_error_func = error_func; + info.buffer = dd->page; + info.buffer_vma = addr + DEFAULT_LOAD_ADDR; + info.buffer_length = dd->page_size; + info.arch = bfd_arch_s390; + info.mach = bfd_mach_s390_64; + disassemble_init_for_target(&info); + print_insn = disassembler(bfd_arch_s390, TRUE, + bfd_mach_s390_64, NULL); + if (!print_insn) + return 0; + ret |= disas_at(dd, &info, 0); + + if (ret > 0 && priv.state.flags & SAM64_SEEN) + dd->arch = "s390x"; + + return ret; +} diff --git a/tools/kdumpid/search.c b/tools/kdumpid/search.c new file mode 100644 index 00000000..83d66c7b --- /dev/null +++ b/tools/kdumpid/search.c @@ -0,0 +1,181 @@ +/* + * search.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include + +#include "kdumpid.h" + +static void +compute_badchar(ssize_t *badchar, const unsigned char *s, ssize_t len) +{ + size_t i = 1; + while (i < len) + badchar[*s++] = i++; +} + +static void +compute_sfx(ssize_t *sfx, const unsigned char *s, ssize_t len) +{ + ssize_t f, g, i; + + sfx[len - 1] = len; + f = 0; /* bogus assignment to silence a warning */ + g = len - 1; + for (i = len - 2; i >= 0; --i) { + if (i > g && sfx[i + len - 1 - f] < i - g) + sfx[i] = sfx[i + len - 1 - f]; + else { + if (i < g) + g = i; + f = i; + while (g >= 0 && s[g] == s[g + len - 1 - f]) + --g; + sfx[i] = f - g; + } + } +} + +static void +compute_goodsfx(ssize_t *goodsfx, const unsigned char *s, ssize_t len) +{ + ssize_t i, j, *sfx = goodsfx + len; + + compute_sfx(sfx, s, len); + + for (i = 0; i < len; ++i) + goodsfx[i] = len; + j = 0; + for (i = len - 1; i >= 0; --i) + if (sfx[i] == i + 1) + for (; j < len - 1 - i; ++j) + if (goodsfx[j] == len) + goodsfx[j] = len - 1 - i; + for (i = 0; i <= len - 2; ++i) + goodsfx[len - 1 - sfx[i]] = len - 1 - i; +} + +/* A helper function for doing cpin forwards or backwards inside the + * find_bytestr() inner loop + */ +static inline void* +search_cpin(struct dump_desc *dd, void *buf, uint64_t addr, size_t len) +{ + if (!dump_cpin(dd, buf, addr, len)) + return buf; + else if (dd->flags & DIF_FORCE) { + memset(buf, 0, len); + return buf; + } else + return NULL; +} + +/* Search for a constant byte string using the Boyer-Moore algorithm. + */ +static inline unsigned char* +search_buf(unsigned char *buf, size_t buflen, + const unsigned char *needle, size_t maxidx, + ssize_t *badchar, ssize_t *goodsfx) +{ + if (!maxidx) + return memchr(buf, *needle, buflen); + + while (buflen > maxidx) { + unsigned char *p; + ssize_t shift, i; + + for (p = buf + maxidx, i = maxidx; i >= 0; --p, --i) + if (needle[i] != *p) + break; + + if (i < 0) + return buf; + + shift = i + 1 - badchar[*p]; + if (shift < goodsfx[i]) + shift = goodsfx[i]; + + buf += shift; + buflen -= shift; + } + return NULL; +} + +/* Search for a constant byte string using the Boyer-Moore algorithm. */ +uint64_t +dump_search_range(struct dump_desc *dd, + uint64_t start, uint64_t end, + const unsigned char *needle, size_t len) +{ + void *dynalloc; + ssize_t *badchar, *goodsfx; + unsigned char *readbuf; + + if (len > 1) { + dynalloc = calloc(sizeof(ssize_t) * (256 + 2*len) + + 2*(len-1), 1); + if (!dynalloc) + return INVALID_ADDR; + badchar = dynalloc; + goodsfx = badchar + 256; + readbuf = dynalloc + sizeof(ssize_t) * (256 + 2*len); + + compute_badchar(badchar, needle, len); + compute_goodsfx(goodsfx, needle, len); + } else { + dynalloc = NULL; + badchar = goodsfx = NULL; + readbuf = NULL; + } + + --len; /* simplify offset computing */ + + while (start < end) { + off_t remain; + unsigned char *p, *q; + + remain = dd->page_size - (start & (dd->page_size - 1)); + if (remain > end - start) + remain = end - start; + + if (remain > len) { + if (read_page(dd, start / dd->page_size)) { + if (! (dd->flags & DIF_FORCE)) + break; + memset(dd->page, 0, dd->page_size); + } + p = dd->page + (start & (dd->page_size - 1)); + } else { + remain += len; + p = search_cpin(dd, readbuf, start, remain); + if (!p) + break; + } + start += remain; + + q = search_buf(p, remain, needle, len, + badchar, goodsfx); + if (q) { + if (dynalloc) + free(dynalloc); + return start + q - p - remain; + } + + start -= len; + } + + if (dynalloc) + free(dynalloc); + return INVALID_ADDR; +} diff --git a/tools/kdumpid/util.c b/tools/kdumpid/util.c new file mode 100644 index 00000000..0e42335c --- /dev/null +++ b/tools/kdumpid/util.c @@ -0,0 +1,518 @@ +/* + * util.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include + +#include "kdumpid.h" + +#define MAX_KERNEL_SIZE 16*1024*1024 + +#define ALLOC_INC 1024 + +static void +chomp(char *banner) +{ + char *p = banner; + while (*p && *p != '\n') + ++p; + *p = 0; +} + +static const char* +get_machine_arch(const char *machine) +{ + if (!strcmp(machine, "i386") || + !strcmp(machine, "i586") || + !strcmp(machine, "i686")) + return "ia32"; + else if (!strcmp(machine, "arm64")) + return "aarch64"; + else if (!strncmp(machine, "arm", 3)) + return "arm"; + + return machine; +} + +static const char* +cfg2arch(const char *cfg) +{ + if (strstr(cfg, "CONFIG_X86_64=y")) + return "x86_64"; + if (strstr(cfg, "CONFIG_X86_32=y")) + return "ia32"; + if (strstr(cfg, "CONFIG_PPC64=y")) + return "ppc64"; + if (strstr(cfg, "CONFIG_PPC32=y")) + return "ppc"; + if (strstr(cfg, "CONFIG_IA64=y")) + return "ia64"; + if (strstr(cfg, "CONFIG_S390=y")) + return strstr(cfg, "CONFIG_64BIT=y") + ? "s390x" + : "s390"; + if (strstr(cfg, "CONFIG_ALPHA=y")) + return "alpha"; + if (strstr(cfg, "CONFIG_ARM=y")) + return "arm"; + return NULL; +} + +static int +arch_in_array(const char *arch, const char *const *arr) +{ + const char *const *p = arr; + if (arch == NULL) + return 1; + while (*p) { + if (!strcmp(arch, *p)) + return 1; + ++p; + } + return 0; +} + +int +get_version_from_banner(struct dump_desc *dd) +{ + const char *p; + char *q; + + if (!*dd->banner) + return -1; + + p = dd->banner + sizeof("Linux version ") - 1; + q = dd->ver; + while (*p && *p != ' ') + *q++ = *p++; + *q = 0; + return 0; +} + +int +need_explore(struct dump_desc *dd) +{ + if (!dd->arch && dd->machine[0]) + dd->arch = get_machine_arch(dd->machine); + + if (!(dd->flags & DIF_VERBOSE) && dd->arch != NULL && dd->ver[0]) + return 0; + return 1; +} + +/* utsname strings are 65 characters long. + * Final NUL may be missing (i.e. corrupted dump data) + */ +static void +copy_uts_string(char *dest, const char *src) +{ + if (!*dest) { + memcpy(dest, src, 65); + dest[65] = 0; + } +} + +static int +uts_looks_sane(struct new_utsname *uts) +{ + return uts->sysname[0] && uts->nodename[0] && uts->release[0] && + uts->version[0] && uts->machine[0]; +} + +int read_page(struct dump_desc *dd, unsigned long pfn) +{ + size_t rd = dd->page_size; + return kdump_read(dd->ctx, KDUMP_KPHYSADDR, pfn * dd->page_size, + dd->page, &rd); +} + +size_t +dump_cpin(struct dump_desc *dd, void *buf, uint64_t paddr, size_t len) +{ + size_t rd = len; + kdump_read(dd->ctx, KDUMP_KPHYSADDR, paddr, buf, &rd); + return len - rd; +} + +int +uncompress_config(struct dump_desc *dd, void *zcfg, size_t zsize) +{ + z_stream stream; + void *cfg; + int ret; + + stream.next_in = zcfg; + stream.avail_in = zsize; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + + if (inflateInit2(&stream, 16+MAX_WBITS) != Z_OK) + return -1; + + cfg = NULL; + stream.avail_out = -1; + stream.total_out = 0; + do { + void *newbuf = realloc(cfg, stream.total_out + 1024); + if (!newbuf) { + ret = Z_MEM_ERROR; + break; + } + + cfg = newbuf; + stream.next_out = cfg + stream.total_out; + stream.avail_out += ALLOC_INC; + } while( (ret = inflate(&stream, Z_NO_FLUSH)) == Z_OK); + + inflateEnd(&stream); + + if (ret != Z_STREAM_END) { + free(cfg); + return -1; + } + + *stream.next_out = 0; /* terminating NUL */ + dd->cfg = cfg; + dd->cfglen = stream.total_out; + + return 0; +} + +typedef int (*explore_fn)(struct dump_desc *, uint64_t, uint64_t, + const char *const *); + +static int +explore_ktext(struct dump_desc *dd, explore_fn fn, + const char *const *expected_archs) +{ + const addrxlat_range_t *range; + const addrxlat_meth_t *meth; + addrxlat_sys_t *xlatsys; + addrxlat_addr_t addr; + addrxlat_map_t *map; + size_t n; + int ret; + + if (kdump_get_addrxlat(dd->ctx, NULL, &xlatsys) != KDUMP_OK) + return -1; + + ret = -1; + meth = addrxlat_sys_get_meth(xlatsys, ADDRXLAT_SYS_METH_KTEXT); + if (meth->kind != ADDRXLAT_LINEAR) + goto out; + addr = meth->param.linear.off; + + map = addrxlat_sys_get_map(xlatsys, ADDRXLAT_SYS_MAP_KV_PHYS); + range = addrxlat_map_ranges(map); + n = addrxlat_map_len(map); + while (n) { + if (range->meth == ADDRXLAT_SYS_METH_KTEXT && + !fn(dd, addr, addr + range->endoff + 1, expected_archs)) { + ret = 0; + goto out; + } + addr += range->endoff + 1; + ++range; + --n; + } + + out: + addrxlat_sys_decref(xlatsys); + return ret; +} + +static int +explore_kernel(struct dump_desc *dd, explore_fn fn) +{ + static const char *const all_archs[] = { + "alpha", "arm", "ia64", + "ppc", "ppc64", + "s390", "s390x", + "ia32", "x86_64", + NULL + }; + static const char *const x86_biarch[] = { + "ia32", "x86_64", NULL + }; + static const char *const zarch[] = { + "s390", "s390x", NULL + }; + static const char *const ppc[] = { "ppc", NULL }; + static const char *const ppc64[] = { "ppc64", NULL }; + + uint64_t addr; + + if (dd->flags & DIF_FORCE) + return fn(dd, 0, dd->max_pfn * dd->page_size, all_archs); + + if (dd->flags & DIF_START_FOUND) + return fn(dd, dd->start_addr, + dd->start_addr + MAX_KERNEL_SIZE, all_archs); + + if (!explore_ktext(dd, fn, all_archs)) + return 0; + + if (arch_in_array(dd->arch, x86_biarch)) { + /* Xen pv kernels are loaded low */ + addr = 0x2000; + if (dd->xen_type != KDUMP_XEN_NONE && + looks_like_kcode_x86(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, x86_biarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + + /* x86 kernels were traditionally loaded at 1M */ + addr = 1024*1024; + if (looks_like_kcode_x86(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, x86_biarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + + /* other x86 kernels are loaded at 16M */ + addr = 16*1024*1024; + if (looks_like_kcode_x86(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, x86_biarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + + /* some x86 kernels are loaded at 2M (due to align) */ + addr = 2*1024*1024; + if (looks_like_kcode_x86(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, x86_biarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + } + + if (arch_in_array(dd->arch, ppc64)) { + /* PPC64 loads at 0 */ + addr = 0; + if (looks_like_kcode_ppc64(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, zarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + } + + if (arch_in_array(dd->arch, ppc)) { + /* POWER also loads at 0 */ + addr = 0; + if (looks_like_kcode_ppc(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, zarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + } + + if (arch_in_array(dd->arch, zarch)) { + /* Linux/390 loads at 0 */ + addr = 0; + if (looks_like_kcode_s390(dd, addr) > 0 && + !fn(dd, addr, addr + MAX_KERNEL_SIZE, zarch)) { + dd->start_addr = addr; + dd->flags |= DIF_START_FOUND; + return 0; + } + } + + return -1; +} + +static int +explore_banner(struct dump_desc *dd, uint64_t addr, uint64_t endaddr, + const char *const *expected_archs) +{ + static const unsigned char banhdr[] = "Linux version "; + + while ((addr = dump_search_range(dd, addr, endaddr, + banhdr, sizeof(banhdr) - 1)) != INVALID_ADDR) { + char banner[256]; + size_t len; + + len = dump_cpin(dd, banner, addr, sizeof banner); + addr += sizeof(banhdr) - 1; + if (len == sizeof banner) + continue; + dd->banner[sizeof(dd->banner)-1] = 0; + strncpy(dd->banner, banner, sizeof(dd->banner) - 1); + chomp(dd->banner); + return 0; + } + + return -1; +} + +static int +explore_utsname(struct dump_desc *dd, uint64_t addr, uint64_t endaddr, + const char *const *expected_archs) +{ + static const unsigned char sysname[65] = "Linux"; + + while ((addr = dump_search_range(dd, addr, endaddr, + sysname, sizeof sysname)) != INVALID_ADDR) { + struct new_utsname uts; + size_t len; + const char *arch; + + len = dump_cpin(dd, &uts, addr, sizeof uts); + addr += sizeof sysname; + if (len) + continue; + + if (!uts_looks_sane(&uts)) + continue; + + arch = get_machine_arch(uts.machine); + if (arch && arch_in_array(arch, expected_archs)) { + copy_uts_string(dd->machine, uts.machine); + copy_uts_string(dd->ver, uts.release); + dd->arch = (arch == uts.machine + ? dd->machine + : arch); + return 0; + } + } + + return -1; +} + +static uint64_t +search_ikcfg(struct dump_desc *dd, uint64_t startaddr, uint64_t endaddr) +{ + const unsigned char magic_start[8] = "IKCFG_ST"; + const unsigned char magic_end[8] = "IKCFG_ED"; + size_t cfgsize; + char *cfg; + + startaddr = dump_search_range(dd, startaddr, endaddr, + magic_start, sizeof magic_start); + if (startaddr == INVALID_ADDR) + goto fail; + startaddr += sizeof(magic_start); + + endaddr = dump_search_range(dd, startaddr, endaddr, + magic_end, sizeof magic_end); + if (endaddr == INVALID_ADDR) + goto fail; + + cfgsize = endaddr - startaddr; + if (! (cfg = malloc(cfgsize)) ) { + perror("Cannot allocate kernel config"); + goto fail; + } + if (dump_cpin(dd, cfg, startaddr, cfgsize)) + goto fail_free; + + if (uncompress_config(dd, cfg, cfgsize)) + goto fail_free; + + free(cfg); + return endaddr; + + fail_free: + free(cfg); + fail: + return INVALID_ADDR; +} + +static int +explore_ikcfg(struct dump_desc *dd, uint64_t addr, uint64_t endaddr, + const char *const *expected_archs) +{ + while ((addr = search_ikcfg(dd, addr, endaddr)) != INVALID_ADDR) { + const char *arch = cfg2arch(dd->cfg); + if (arch && arch_in_array(arch, expected_archs)) { + dd->arch = arch; + return 0; + } + } + + return -1; +} + +int +explore_raw_data(struct dump_desc *dd) +{ + addrxlat_sys_t *sys; + addrxlat_map_t *map; + kdump_status kstatus; + int ret; + + if ( (dd->page = malloc(dd->page_size)) == NULL) { + perror("Cannot allocate page data"); + return -1; + } + + ret = -1; + + kstatus = kdump_get_addrxlat(dd->ctx, NULL, &sys); + if (kstatus != KDUMP_OK) { + fprintf(stderr, "Cannot get address translation: %s\n", + kdump_get_err(dd->ctx)); + goto err_free; + } + + map = addrxlat_sys_get_map(sys, ADDRXLAT_SYS_MAP_KPHYS_MACHPHYS); + if (!map) { + addrxlat_range_t range; + addrxlat_meth_t meth; + addrxlat_status status; + + meth.kind = ADDRXLAT_LINEAR; + meth.target_as = ADDRXLAT_MACHPHYSADDR; + meth.param.linear.off = 0; + addrxlat_sys_set_meth(sys, ADDRXLAT_SYS_METH_KPHYS_MACHPHYS, + &meth); + + map = addrxlat_map_new(); + if (!map) { + perror("Cannot allocate identity map"); + goto err_xlat; + } + + range.endoff = ADDRXLAT_ADDR_MAX; + range.meth = ADDRXLAT_SYS_METH_KPHYS_MACHPHYS; + status = addrxlat_map_set(map, 0, &range); + if (status != ADDRXLAT_OK) { + fprintf(stderr, "Cannot set identity range: %s\n", + addrxlat_strerror(status)); + goto err_xlat; + } + addrxlat_sys_set_map(sys, ADDRXLAT_SYS_MAP_KPHYS_MACHPHYS, map); + } + + ret &= explore_kernel(dd, explore_utsname); + explore_kernel(dd, explore_ikcfg); + ret &= explore_kernel(dd, explore_banner); + + err_xlat: + addrxlat_sys_decref(sys); + err_free: + free(dd->page); + + return ret; +} diff --git a/tools/kdumpid/x86.c b/tools/kdumpid/x86.c new file mode 100644 index 00000000..5e357782 --- /dev/null +++ b/tools/kdumpid/x86.c @@ -0,0 +1,313 @@ +#include +#include +#include + +#include + +#include "kdumpid.h" + +#define MAX_INSN_LEN 100 + +struct disas_state { + unsigned long flags; + uint64_t sp_value; + uint32_t ecx_value; + int depth; +}; + +#define SI_STORED 1 +#define SI_MODIFIED 2 +#define SP_MODIFIED 4 + +struct disas_priv { + char *iptr; + struct disas_state initstate; + + char insn[MAX_INSN_LEN]; + unsigned char pagemap[]; +}; + +static const unsigned char xen_cpuid[] = + { 0x0f, 0x0b, 0x78, 0x65, 0x6e, 0x0f, 0xa2 }; + +#define MSR_GS_BASE 0xc0000101 + +static const char sep[] = ", \t\r\n"; +#define wsep (sep+1) + +static disassembler_ftype print_insn; + +static void +append_insn(void *data, const char *fmt, va_list va) +{ + struct disas_priv *priv = data; + size_t remain; + int len; + + remain = priv->insn + sizeof(priv->insn) - priv->iptr; + len = vsnprintf(priv->iptr, remain, fmt, va); + if (len > 0) + priv->iptr += len; +} + +static int +disas_fn(void *data, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#ifdef DIS_ASM_STYLED_PRINTF + +static int +disas_styled_fn(void *data, enum disassembler_style style, + const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + append_insn(data, fmt, va); + va_end(va); + + return 0; +} + +#endif /* DIS_ASM_STYLED_PRINTF */ + +static void error_func(int status, bfd_vma memaddr, + struct disassemble_info *dinfo) +{ + /* intentionally empty */ +} + +static size_t +skip_zeroes(unsigned char *buf, size_t len) +{ + size_t num = 0; + while (len-- && !*buf++) + ++num; + return (num > 2) ? num : 0; +} + +static int +check_xen_early_idt_msg(struct dump_desc *dd) +{ + static const unsigned char msg[] = + "PANIC: early exception rip %lx error %lx cr2 %lx\n"; + void *p = dd->page; + + while (p < dd->page + 0x100) + if (!memcmp(p, msg, sizeof msg - 1)) + return 1; + return 0; +} + +static void +set_pagemap(unsigned char *pagemap, unsigned pc, int count) +{ + while (count > 0) { + pagemap[pc >> 3] |= 1 << (pc & 7); + ++pc, --count; + } +} + +static int +is_lgdt(const char *insn) +{ + return insn && (!strcmp(insn, "lgdt") || !strcmp(insn, "lgdtl")); +} + +static int +is_reg(const char *loc, const char *reg) +{ + if (!loc || *loc++ != '%') + return 0; + if (*loc == 'r' || *loc == 'e') + ++loc; + return !strcmp(loc, reg); +} + +static int +looks_like_kvaddr(struct disassemble_info *info, uint64_t addr) +{ + if (info->mach == bfd_mach_i386_i386) { + if (addr > 0xffffffff) + return 0; + + /* TODO: handle other Memory split options + * than the default VMSPLIT_3G + */ + if (addr >= 0xc0000000) + return 1; + } else if (info->mach == bfd_mach_x86_64) { + if (addr >= 0xffffffff80000000) + return 1; + } + + return 0; +} + +static int +disas_at(struct dump_desc *dd, struct disassemble_info *info, unsigned pc) +{ + struct disas_priv *priv = info->stream; + struct disas_state state = priv->initstate; + char *toksave; + char *insn, *arg1, *arg2; + unsigned long long a; + int count; + + do { + count = skip_zeroes(dd->page + pc, dd->page_size - pc); + set_pagemap(priv->pagemap, pc, count); + pc += count; + + if (dd->page_size - pc == 0) + break; + + if (dd->page_size - pc >= sizeof(xen_cpuid) && + !memcmp(dd->page + pc, xen_cpuid, sizeof xen_cpuid)) + return 1; + + if ( (priv->pagemap[pc >> 3] & (1 << (pc & 7))) ) + break; + + priv->iptr = priv->insn; + count = print_insn(info->buffer_vma + pc, info); + set_pagemap(priv->pagemap, pc, count); + if (count < 0) + break; + pc += count; + + insn = strtok_r(priv->insn, wsep, &toksave); + arg1 = strtok_r(NULL, sep, &toksave); + arg2 = strtok_r(NULL, sep, &toksave); + + /* a jump instruction? */ + if ( (*insn == 'j' || + !strncmp(insn, "call", 4)) && + sscanf(arg1, "0x%llx", &a) == 1) { + int cont = strncmp(insn, "jmp", 3); + + a -= info->buffer_vma; + if (a < dd->page_size) { + priv->initstate = state; + ++priv->initstate.depth; + if (disas_at(dd, info, a) > 0) + return 1; + --priv->initstate.depth; + } + + if (cont) + continue; + + if (!state.depth && dd->xen_type != KDUMP_XEN_NONE + && state.flags & SI_STORED) { + if (state.flags & SP_MODIFIED && + looks_like_kvaddr(info, state.sp_value)) + return 1; + return check_xen_early_idt_msg(dd); + } + + break; + } + if (!strncmp(insn, "ret", 3)) + break; + + if (!strcmp(insn, "(bad)")) + return -1; + + if (is_lgdt(insn)) + return 1; + + if (!strcmp(insn, "wrmsr") && state.ecx_value == MSR_GS_BASE) + return 1; + + if (!strncmp(insn, "mov", 3)) { + if (is_reg(arg2, "cx") && + sscanf(arg1, "$0x%llx", &a) == 1) + state.ecx_value = a; + else if (is_reg(arg2, "sp") && + sscanf(arg1, "$0x%llx", &a) == 1) { + state.sp_value = a; + state.flags |= SP_MODIFIED; + } + else if (!strcmp(arg2, "%cr3") || + !strcmp(arg2, "%cr4")) + return 1; + if (is_reg(arg1, "si")) { + state.flags |= SI_STORED; + if (dd->xen_type != KDUMP_XEN_NONE && + !(state.flags & SI_MODIFIED) && + sscanf(arg2, "0x%llx", &a) == 1) + dd->xen_start_info = a; + } + } + + if (is_reg(arg2, "si")) + state.flags |= SI_MODIFIED; + } while (count > 0); + + return 0; +} + +/* Decode the first page at addr and check whether it looks like + * x86 kernel code start. + */ +int +looks_like_kcode_x86(struct dump_desc *dd, uint64_t addr) +{ + struct disassemble_info info; + struct disas_priv *priv; + + if (read_page(dd, addr / dd->page_size)) + return -1; + + priv = calloc(1, sizeof(struct disas_priv) + dd->page_size / 8); + if (!priv) + return -1; + +#ifdef DIS_ASM_STYLED_PRINTF + init_disassemble_info(&info, priv, disas_fn, disas_styled_fn); +#else + init_disassemble_info(&info, priv, disas_fn); +#endif + info.memory_error_func = error_func; + info.buffer = dd->page; + info.buffer_vma = addr; + info.buffer_length = dd->page_size; + info.arch = bfd_arch_i386; + + /* Try i386 code first */ + info.mach = bfd_mach_i386_i386; + disassemble_init_for_target(&info); + print_insn = disassembler(bfd_arch_i386, FALSE, + bfd_mach_i386_i386, NULL); + if ((!dd->arch || strcmp(dd->arch, "x86_64")) && + print_insn && + disas_at(dd, &info, 0) > 0) { + free(priv); + return 1; + } + + /* Try x86_64 if that failed */ + memset(priv, 0, sizeof(struct disas_priv) + dd->page_size / 8); + info.mach = bfd_mach_x86_64; + disassemble_init_for_target(&info); + print_insn = disassembler(bfd_arch_i386, FALSE, + bfd_mach_x86_64, NULL); + if ((!dd->arch || strcmp(dd->arch, "i386")) && + print_insn && + disas_at(dd, &info, 0) > 0) { + free(priv); + return 1; + } + + free(priv); + return 0; +}