Patchwork [RFC,v5,4/9] Add basic Linux kernel support

login
register
mail settings
Submitter Philipp Rudo
Date March 12, 2018, 3:31 p.m.
Message ID <20180312153115.47321-5-prudo@linux.vnet.ibm.com>
Download mbox | patch
Permalink /patch/26292/
State New
Headers show

Comments

Philipp Rudo - March 12, 2018, 3:31 p.m.
Implement the basic infrastructure and functionality to allow Linux kernel
debugging with GDB.  This contains handling of kernel symbols and data
structures as well as a simple target_ops to hook into GDB.  For the code
to work architectures must provide an implementation for the virtual
methods in linux_kernel_ops.

For simplicity this patch only supports static targets, i.e. core dumps.
Support for live debugging will be provided in a separate patch.

gdb/ChangeLog:

	* gdbarch.sh (get_new_lk_ops): New hook.
	* gdbarch.h: Regenerated.
	* gdbarch.c: Regenerated.
	* defs.h (gdb_osabi): Add GDB_OSABI_LINUX_KERNEL.
	* osabi.c (gdb_osabi_names): Add Linux kernel entry.
	* lk-low.h: New file.
	* lk-low.c: New file.
	* lk-list.h: New file.
	* lk-bitmap.h: New file.
	* Makefile.in (ALLDEPFILES): Add lk-low.c.
	(HFILES_NO_SRCDIR): Add lk-low.h.
	(ALL_TARGET_OBS): Add lk-low.o.
	* configure.tgt (lk_tobjs): New variable with object files for Linux
	kernel support.
	(s390*-*-linux*): Add lk_tobjs.
---
 gdb/Makefile.in   |   3 +
 gdb/configure.tgt |   7 +-
 gdb/defs.h        |   1 +
 gdb/gdbarch.c     |  32 ++
 gdb/gdbarch.h     |   9 +
 gdb/gdbarch.sh    |   4 +
 gdb/lk-bitmap.h   | 226 ++++++++++++++
 gdb/lk-list.h     | 201 +++++++++++++
 gdb/lk-low.c      | 864 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gdb/lk-low.h      | 335 +++++++++++++++++++++
 gdb/osabi.c       |   1 +
 11 files changed, 1682 insertions(+), 1 deletion(-)
 create mode 100644 gdb/lk-bitmap.h
 create mode 100644 gdb/lk-list.h
 create mode 100644 gdb/lk-low.c
 create mode 100644 gdb/lk-low.h
Kamil Rytarowski - March 13, 2018, 2:08 p.m.
On 12.03.2018 16:31, Philipp Rudo wrote:
> Implement the basic infrastructure and functionality to allow Linux kernel
> debugging with GDB.  This contains handling of kernel symbols and data
> structures as well as a simple target_ops to hook into GDB.  For the code
> to work architectures must provide an implementation for the virtual
> methods in linux_kernel_ops.
> 
> For simplicity this patch only supports static targets, i.e. core dumps.
> Support for live debugging will be provided in a separate patch.
> 


What's wrong with kgdb? It works well on NetBSD for alive and dead
kernels since 90ties.
Philipp Rudo - March 14, 2018, 9:48 a.m.
Hi Kamil,

On Tue, 13 Mar 2018 15:08:32 +0100
Kamil Rytarowski <n54@gmx.com> wrote:

> On 12.03.2018 16:31, Philipp Rudo wrote:
> > Implement the basic infrastructure and functionality to allow Linux kernel
> > debugging with GDB.  This contains handling of kernel symbols and data
> > structures as well as a simple target_ops to hook into GDB.  For the code
> > to work architectures must provide an implementation for the virtual
> > methods in linux_kernel_ops.
> > 
> > For simplicity this patch only supports static targets, i.e. core dumps.
> > Support for live debugging will be provided in a separate patch.
> >   
> 
> 
> What's wrong with kgdb? It works well on NetBSD for alive and dead
> kernels since 90ties.

I don't know kgdb well. We at IBM mostly work with dumps and the crash tool,
which is based on an old version of gdb and has some drawbacks. For example it
doesn't have a dwarf unwinder. That's why we wanted to see if the logic needed
for kernel debugging can be added directly to gdb, instead of having a hack
working on top of it.

Furthermore, I don't see a conflict between kgdb and this work. The way I see
it, kgdb is a technique to retrieve information from the kernel. This work
however, teaches gdb how to interpret the data from the kernel in order to make
it easier to find the information you need.

Thanks
Philipp
Kamil Rytarowski - March 14, 2018, 11:40 p.m.
On 14.03.2018 10:48, Philipp Rudo wrote:
> Hi Kamil,
> 
> On Tue, 13 Mar 2018 15:08:32 +0100
> Kamil Rytarowski <n54@gmx.com> wrote:
> 
>> On 12.03.2018 16:31, Philipp Rudo wrote:
>>> Implement the basic infrastructure and functionality to allow Linux kernel
>>> debugging with GDB.  This contains handling of kernel symbols and data
>>> structures as well as a simple target_ops to hook into GDB.  For the code
>>> to work architectures must provide an implementation for the virtual
>>> methods in linux_kernel_ops.
>>>
>>> For simplicity this patch only supports static targets, i.e. core dumps.
>>> Support for live debugging will be provided in a separate patch.
>>>   
>>
>>
>> What's wrong with kgdb? It works well on NetBSD for alive and dead
>> kernels since 90ties.
> 
> I don't know kgdb well. We at IBM mostly work with dumps and the crash tool,
> which is based on an old version of gdb and has some drawbacks. For example it
> doesn't have a dwarf unwinder. That's why we wanted to see if the logic needed
> for kernel debugging can be added directly to gdb, instead of having a hack
> working on top of it.
> 
> Furthermore, I don't see a conflict between kgdb and this work. The way I see
> it, kgdb is a technique to retrieve information from the kernel. This work
> however, teaches gdb how to interpret the data from the kernel in order to make
> it easier to find the information you need.
> 

I see. Once you will be done, I will have a look whether it can be
useful for other OSes.

I've mentioned kgdb, as it has been ported to Linux and it can work as a
backend for GDB.

> Thanks
> Philipp
>
Simon Marchi - March 19, 2018, 12:11 a.m.
On 2018-03-12 11:31 AM, Philipp Rudo wrote:
> Implement the basic infrastructure and functionality to allow Linux kernel
> debugging with GDB.  This contains handling of kernel symbols and data
> structures as well as a simple target_ops to hook into GDB.  For the code
> to work architectures must provide an implementation for the virtual
> methods in linux_kernel_ops.
> 
> For simplicity this patch only supports static targets, i.e. core dumps.
> Support for live debugging will be provided in a separate patch.

Hi Phlipp,

I'm going back and forth trying to understand how this interacts with the rest
of GDB.  Meanwhile, could you explain a little bit how this (well, the whole
patchset) is expected to be used, from the user point of view?  How do you
setup a coredump debugging session, and eventually a live remote one?  Does the
user see one or multiple inferiors?  What threads do they see, only kernel threads,
or kernel + userspace?

Here some minor/formatting things I noted while reading the code, it's not meant to
be a full review (I don't feel like I understand well enough yet), but I thought
I would share them anyway since I wrote them.

> +/* Helper function for try_declare_type.  Returns type on success or NULL on
> +   failure  */
> +
> +static struct type *
> +lk_find_type (const std::string &name)
> +{
> +  const struct block *global;
> +  const struct symbol *sym;
> +
> +  global = block_global_block(get_selected_block (0));

Missing space.

> +  sym = lookup_symbol (name.c_str (), global, STRUCT_DOMAIN, NULL).symbol;
> +  if (sym != NULL)
> +    return SYMBOL_TYPE (sym);
> +
> +  /*  Chek for "typedef struct { ... } name;"-like definitions.  */

"Check"

> +  /* True when all fiels have been parsed.  */

"all fields"

> +  bool empty () const
> +  { return m_end == std::string::npos; }
> +
> +  /* Return the depth, i.e. number of fields, in m_alias.  */
> +  unsigned int depth () const
> +  {
> +    size_t pos = m_alias.find (delim);
> +    unsigned int ret = 0;
> +
> +    while (pos != std::string::npos)
> +      {
> +	ret ++;
> +	pos = m_alias.find (delim, pos + delim.size ());
> +      }
> +
> +    return ret;
> +  }
> +
> +private:
> +  /* Alias originally passed to parser.  */
> +  std::string m_alias;
> +
> +  /* First index of current field in m_alias.  */
> +  size_t m_start = 0;
> +
> +  /* Last index of current field in m_alias.  */
> +  size_t m_end = 0;
> +
> +  /* Type of the last field found.  Needed to get s_name of embedded
> +     fields.  */
> +  struct type *m_last_type = NULL;
> +
> +  /* Delemiter used to separate fields.  */

Delimiter.

> +/* Helper functions to read and return basic types at a given ADDRess.  */
> +
> +/* Read and return the integer value at address ADDR.  */

Comments for non-static functions should be /* See lk-low.h.  */ and documented
in lk-low.h, there are a few instances.

> --- /dev/null
> +++ b/gdb/lk-low.h
> @@ -0,0 +1,335 @@
> +/* Basic Linux kernel support, architecture independent.
> +
> +   Copyright (C) 2016 Free Software Foundation, Inc.
> +
> +   This file is part of GDB.
> +
> +   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 3 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.
> +
> +   You should have received a copy of the GNU General Public License
> +   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
> +
> +#ifndef __LK_LOW_H__
> +#define __LK_LOW_H__
> +
> +#include <unordered_map>
> +
> +#include "gdbtypes.h"
> +#include "target.h"
> +
> +/* Copied constants defined in Linux kernel.  */
> +#define LK_TASK_COMM_LEN 16
> +#define LK_BITS_PER_BYTE 8
> +
> +/* Definitions used in linux kernel target.  */
> +#define LK_CPU_INVAL -1U
> +
> +/* Helper functions to read and return a value at a given ADDRess.  */
> +extern int lk_read_int (CORE_ADDR addr);
> +extern unsigned int lk_read_uint (CORE_ADDR addr);
> +extern LONGEST lk_read_long (CORE_ADDR addr);
> +extern ULONGEST lk_read_ulong (CORE_ADDR addr);
> +extern CORE_ADDR lk_read_addr (CORE_ADDR addr);
> +
> +/* Enum to track the config options used to build the kernel.  Whenever
> +   a symbol is declared (in linux_kernel_ops::{arch_}read_symbols) which
> +   only exists if the kernel was built with a certain config option an entry
> +   has to be added here.  */
> +enum lk_kconfig_values
> +{
> +  LK_CONFIG_ALWAYS	= 1 << 0,
> +  LK_CONFIG_SMT		= 1 << 1,
> +  LK_CONFIG_MODULES	= 1 << 2,
> +};
> +DEF_ENUM_FLAGS_TYPE (enum lk_kconfig_values, lk_kconfig);
> +
> +/* We use the following convention for PTIDs:
> +
> +   ptid->pid = inferiors PID
> +   ptid->lwp = PID from task_stuct
> +   ptid->tid = address of task_struct
> +
> +   The task_structs address as TID has two reasons.  First, we need it quite
> +   often and there is no other reasonable way to pass it down.  Second, it
> +   helps us to distinguish swapper tasks as they all have PID = 0.
> +
> +   Furthermore we cannot rely on the target beneath to use the same PID as the
> +   task_struct. Thus we need a mapping between our PTID and the PTID of the
> +   target beneath. Otherwise it is impossible to pass jobs, e.g. fetching
> +   registers of running tasks, to the target beneath.  */
> +
> +/* Private data struct to map between our and the target beneath PTID.  */
> +
> +struct lk_ptid_map
> +{
> +  struct lk_ptid_map *next;
> +  unsigned int cpu;
> +  ptid_t old_ptid;

I don't really understand the usage of the name "old_ptid".  If it's the ptid
of the target beneath, why call it "old" and not "beneath_ptid", for example?
It makes it sound like its value changed in time.

> +  /* Check whether the kernel was build using this config option.  */

"was built"

> +  /* Collection of all declared symbols (addresses, fields etc.).  */
> +  std::unordered_map<std::string, union lk_symbol> m_symbols;

Is there a reason to put all symbols in the same map?  It creates the risk of
misusing a symbol using the wrong type, e.g.:

  try_declare_address ("foo", "foo")

then later

  m_symbols.field ("foo")

Is there a reason not to use three different maps?

Thanks,

Simon

Patch

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 690653ac04..056333e2cd 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -715,6 +715,7 @@  ALL_TARGET_OBS = \
 	iq2000-tdep.o \
 	linux-record.o \
 	linux-tdep.o \
+	lk-low.o \
 	lm32-tdep.o \
 	m32c-tdep.o \
 	m32r-linux-tdep.o \
@@ -1276,6 +1277,7 @@  HFILES_NO_SRCDIR = \
 	linux-nat.h \
 	linux-record.h \
 	linux-tdep.h \
+	lk-low.h \
 	location.h \
 	m2-lang.h \
 	m32r-tdep.h \
@@ -2256,6 +2258,7 @@  ALLDEPFILES = \
 	linux-fork.c \
 	linux-record.c \
 	linux-tdep.c \
+	lk-low.c \
 	lm32-tdep.c \
 	m32r-linux-nat.c \
 	m32r-linux-tdep.c \
diff --git a/gdb/configure.tgt b/gdb/configure.tgt
index ba90411782..be68ac50fc 100644
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -40,6 +40,10 @@  esac
 i386_tobjs="i386-tdep.o arch/i386.o i387-tdep.o"
 amd64_tobjs="amd64-tdep.o arch/amd64.o"
 
+# List of objectfiles for Linux kernel support.  To be included into *-linux*
+# targets wich support Linux kernel debugging.
+lk_tobjs="lk-low.o"
+
 # Here are three sections to get a list of target specific object
 # files according to target triplet $TARG.
 
@@ -516,7 +520,8 @@  powerpc*-*-*)
 s390*-*-linux*)
 	# Target: S390 running Linux
 	gdb_target_obs="s390-linux-tdep.o s390-tdep.o solib-svr4.o \
-			linux-tdep.o linux-record.o symfile-mem.o"
+			linux-tdep.o linux-record.o symfile-mem.o \
+			${lk_tobjs}"
 	build_gdbserver=yes
 	;;
 
diff --git a/gdb/defs.h b/gdb/defs.h
index 91988758a3..692a7b8407 100644
--- a/gdb/defs.h
+++ b/gdb/defs.h
@@ -490,6 +490,7 @@  enum gdb_osabi
   GDB_OSABI_HURD,
   GDB_OSABI_SOLARIS,
   GDB_OSABI_LINUX,
+  GDB_OSABI_LINUX_KERNEL,
   GDB_OSABI_FREEBSD,
   GDB_OSABI_NETBSD,
   GDB_OSABI_OPENBSD,
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index b8703e5a55..fd37c51f6b 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -352,6 +352,7 @@  struct gdbarch
   gdbarch_addressable_memory_unit_size_ftype *addressable_memory_unit_size;
   char ** disassembler_options;
   const disasm_options_t * valid_disassembler_options;
+  gdbarch_get_new_lk_ops_ftype *get_new_lk_ops;
 };
 
 /* Create a new ``struct gdbarch'' based on information provided by
@@ -713,6 +714,7 @@  verify_gdbarch (struct gdbarch *gdbarch)
   /* Skip verify of addressable_memory_unit_size, invalid_p == 0 */
   /* Skip verify of disassembler_options, invalid_p == 0 */
   /* Skip verify of valid_disassembler_options, invalid_p == 0 */
+  /* Skip verify of get_new_lk_ops, has predicate.  */
   if (!log.empty ())
     internal_error (__FILE__, __LINE__,
                     _("verify_gdbarch: the following are invalid ...%s"),
@@ -1058,6 +1060,12 @@  gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
                       "gdbarch_dump: get_longjmp_target = <%s>\n",
                       host_address_to_string (gdbarch->get_longjmp_target));
   fprintf_unfiltered (file,
+                      "gdbarch_dump: gdbarch_get_new_lk_ops_p() = %d\n",
+                      gdbarch_get_new_lk_ops_p (gdbarch));
+  fprintf_unfiltered (file,
+                      "gdbarch_dump: get_new_lk_ops = <%s>\n",
+                      host_address_to_string (gdbarch->get_new_lk_ops));
+  fprintf_unfiltered (file,
                       "gdbarch_dump: gdbarch_get_siginfo_type_p() = %d\n",
                       gdbarch_get_siginfo_type_p (gdbarch));
   fprintf_unfiltered (file,
@@ -5077,6 +5085,30 @@  set_gdbarch_valid_disassembler_options (struct gdbarch *gdbarch,
   gdbarch->valid_disassembler_options = valid_disassembler_options;
 }
 
+int
+gdbarch_get_new_lk_ops_p (struct gdbarch *gdbarch)
+{
+  gdb_assert (gdbarch != NULL);
+  return gdbarch->get_new_lk_ops != NULL;
+}
+
+linux_kernel_ops *
+gdbarch_get_new_lk_ops (struct gdbarch *gdbarch, struct target_ops *target)
+{
+  gdb_assert (gdbarch != NULL);
+  gdb_assert (gdbarch->get_new_lk_ops != NULL);
+  if (gdbarch_debug >= 2)
+    fprintf_unfiltered (gdb_stdlog, "gdbarch_get_new_lk_ops called\n");
+  return gdbarch->get_new_lk_ops (gdbarch, target);
+}
+
+void
+set_gdbarch_get_new_lk_ops (struct gdbarch *gdbarch,
+                            gdbarch_get_new_lk_ops_ftype get_new_lk_ops)
+{
+  gdbarch->get_new_lk_ops = get_new_lk_ops;
+}
+
 
 /* Keep a registry of per-architecture data-pointers required by GDB
    modules.  */
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 5cb131de1d..d1f54c08c9 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -65,6 +65,7 @@  struct mem_range;
 struct syscalls_info;
 struct thread_info;
 struct ui_out;
+class linux_kernel_ops;
 
 #include "regcache.h"
 
@@ -1554,6 +1555,14 @@  extern void set_gdbarch_disassembler_options (struct gdbarch *gdbarch, char ** d
 extern const disasm_options_t * gdbarch_valid_disassembler_options (struct gdbarch *gdbarch);
 extern void set_gdbarch_valid_disassembler_options (struct gdbarch *gdbarch, const disasm_options_t * valid_disassembler_options);
 
+/* Return a new instance of a class inherited from linux_kernel_ops */
+
+extern int gdbarch_get_new_lk_ops_p (struct gdbarch *gdbarch);
+
+typedef linux_kernel_ops * (gdbarch_get_new_lk_ops_ftype) (struct gdbarch *gdbarch, struct target_ops *target);
+extern linux_kernel_ops * gdbarch_get_new_lk_ops (struct gdbarch *gdbarch, struct target_ops *target);
+extern void set_gdbarch_get_new_lk_ops (struct gdbarch *gdbarch, gdbarch_get_new_lk_ops_ftype *get_new_lk_ops);
+
 /* Definition for an unknown syscall, used basically in error-cases.  */
 #define UNKNOWN_SYSCALL (-1)
 
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 33dfa6b349..80167f2dc2 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -1160,6 +1160,9 @@  m;int;addressable_memory_unit_size;void;;;default_addressable_memory_unit_size;;
 v;char **;disassembler_options;;;0;0;;0;pstring_ptr (gdbarch->disassembler_options)
 v;const disasm_options_t *;valid_disassembler_options;;;0;0;;0;host_address_to_string (gdbarch->valid_disassembler_options)
 
+# Return a new instance of a class inherited from linux_kernel_ops
+M;linux_kernel_ops *;get_new_lk_ops;struct target_ops *target;target
+
 EOF
 }
 
@@ -1285,6 +1288,7 @@  struct mem_range;
 struct syscalls_info;
 struct thread_info;
 struct ui_out;
+class linux_kernel_ops;
 
 #include "regcache.h"
 
diff --git a/gdb/lk-bitmap.h b/gdb/lk-bitmap.h
new file mode 100644
index 0000000000..1247e7f9fb
--- /dev/null
+++ b/gdb/lk-bitmap.h
@@ -0,0 +1,226 @@ 
+/* Iterator for bitmaps from the Linux kernel.
+
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 3 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.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef __LK_BITMAP_H__
+#define __LK_BITMAP_H__
+
+#include "defs.h"
+
+#include "lk-low.h"
+
+/* Short hand access to frequently used bitmap.  */
+#define lk_cpu_online_mask lk_bitmap ("cpu_online_mask", "cpumask->bits")
+
+/* Container class to handle bitmaps declared with DECLARE_BITMAP from
+   <linux>/include/linux/types.h.  */
+
+class lk_bitmap
+{
+public:
+
+  template<class T>
+  class base_iterator
+  : public std::iterator<std::bidirectional_iterator_tag, T>
+  {
+  public:
+    base_iterator (const base_iterator<T> &it) = default;
+    base_iterator (std::vector<unsigned long>::const_iterator start,
+		   size_t bit, size_t size)
+      : m_start (start), m_bit (bit), m_size (size)
+    { next (); }
+
+    base_iterator<T> &operator++ ()
+    { m_bit++; return next (); }
+
+    base_iterator<T> operator++ (int)
+    { base_iterator<T> retval = *this; ++(*this); return retval; }
+
+    base_iterator<T> &operator-- ()
+    { m_bit--; return prev (); }
+
+    base_iterator<T> operator-- (int)
+    { base_iterator<T> retval = *this; --(*this); return retval; }
+
+    bool operator== (base_iterator<T> other) const
+    { return (m_start == other.m_start && m_bit == other.m_bit
+	      && m_size == other.m_size); }
+
+    bool operator!= (base_iterator<T> other) const
+    { return !(*this == other); }
+
+    T operator* () const
+    { return m_bit; }
+
+  private:
+    /* Start of the vector containing the bitmap.  */
+    std::vector<unsigned long>::const_iterator m_start;
+
+    /* Last set bit returned.  */
+    size_t m_bit;
+
+    /* Size of the bitmap in bit.  */
+    size_t m_size;
+
+    /* Get next set bit.  */
+    base_iterator<T> &next ();
+
+    /* Get previous set bit.  */
+    base_iterator<T> &prev ();
+  }; /* class base_iterator  */
+
+  /* Constructor for bitmaps defined as variable NAME.  */
+  inline lk_bitmap (const std::string &name);
+
+  /* Constructor for bitmaps defined as field in variable NAME.  */
+  inline lk_bitmap (const std::string &name, const std::string &alias);
+
+  typedef base_iterator<size_t> iterator;
+  typedef base_iterator<const size_t> const_iterator;
+
+  iterator begin () { return iterator (m_bitmap.cbegin (), 0, size ()); }
+  iterator end () { return iterator (m_bitmap.cbegin (), size (), size ()); }
+
+  const_iterator cbegin () const
+  { return const_iterator (m_bitmap.cbegin (), 0, size ()); }
+  const_iterator cend () const
+  { return const_iterator (m_bitmap.cbegin (), size (), size ()); }
+
+  const_iterator begin () const
+  { return this->cbegin (); }
+  const_iterator end () const
+  { return this->cend (); }
+
+  /* Returns size of bitmap in bits.  */
+  inline size_t size () const;
+
+  /* Returns Hamming weight, i.e. number of set bits, of bitmap.  */
+  inline size_t hweight () const;
+
+private:
+  /* Read content of bitmap NAME.  */
+  inline void read (const std::string &name);
+
+  /* Returns number of unsigned longs needed to store N bytes.  */
+  inline size_t byte_to_ulong (size_t n) const;
+
+  /* Storage for content of bitmap.  */
+  std::vector<unsigned long> m_bitmap;
+}; /* class bitmap  */
+
+/* see declaration.  */
+
+template<class T>
+lk_bitmap::base_iterator<T> &
+lk_bitmap::base_iterator<T>::next ()
+{
+  size_t ulong_bits = lk_builtin_type_size (unsigned_long) * LK_BITS_PER_BYTE;
+  auto ulong = m_start + m_bit / ulong_bits;
+  while (m_bit < m_size)
+  {
+    if (*ulong & (1 << m_bit))
+	return *this;
+
+    m_bit++;
+    if ((m_bit % ulong_bits) == 0)
+      ulong++;
+  }
+  return *this;
+}
+
+/* see declaration.  */
+
+template<class T>
+lk_bitmap::base_iterator<T> &
+lk_bitmap::base_iterator<T>::prev ()
+{
+  size_t ulong_bits = lk_builtin_type_size (unsigned_long) * LK_BITS_PER_BYTE;
+  auto ulong = m_start + m_bit / ulong_bits;
+  while (m_bit > m_size)
+  {
+    if (*ulong & (1 << m_bit))
+	return *this;
+
+    m_bit--;
+    if ((m_bit % ulong_bits) == 0)
+      ulong--;
+  }
+  return *this;
+}
+
+/* see declaration.  */
+
+lk_bitmap::lk_bitmap (const std::string &name)
+{
+  symbol *sym = lookup_symbol (name.c_str (), NULL, VAR_DOMAIN, NULL).symbol;
+  size_t size = TYPE_LENGTH (check_typedef (SYMBOL_TYPE (sym)));
+
+  m_bitmap.resize (byte_to_ulong (size));
+  read (name);
+}
+
+/* see declaration.  */
+
+lk_bitmap::lk_bitmap (const std::string &name, const std::string &alias)
+{
+  field *field = lk_field (alias);
+  m_bitmap.resize (byte_to_ulong (FIELD_SIZE (field)));
+  read (name);
+}
+
+/* see declaration.  */
+
+void
+lk_bitmap::read (const std::string &name)
+{
+  size_t ulong_size = lk_builtin_type_size (unsigned_long);
+  CORE_ADDR addr = lk_address (name);
+
+  for (size_t i = 0; i < m_bitmap.size (); i++)
+    m_bitmap[i] = lk_read_ulong (addr + i * ulong_size);
+}
+
+/* see declaration.  */
+size_t
+lk_bitmap::byte_to_ulong (size_t n) const
+{
+  size_t ulong_size = lk_builtin_type_size (unsigned_long);
+  return (n + ulong_size - 1) / ulong_size;
+}
+
+/* see declaration.  */
+
+size_t
+lk_bitmap::size () const
+{
+  size_t ulong_size = lk_builtin_type_size (unsigned_long);
+  return (m_bitmap.size () * ulong_size * LK_BITS_PER_BYTE);
+}
+
+/* see declaration.  */
+
+size_t
+lk_bitmap::hweight () const
+{
+  size_t ret = 0;
+  for (auto bit : *this)
+    ret++;
+  return ret;
+}
+
+#endif /* __LK_BITMAP_H__ */
diff --git a/gdb/lk-list.h b/gdb/lk-list.h
new file mode 100644
index 0000000000..512a47ba08
--- /dev/null
+++ b/gdb/lk-list.h
@@ -0,0 +1,201 @@ 
+/* Iterators for internal data structures of the Linux kernel.
+
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 3 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.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef __LK_LIST_H__
+#define __LK_LIST_H__
+
+#include "defs.h"
+
+#include "inferior.h"
+#include "lk-low.h"
+
+
+/* Container class to handle doubly linked list using struct list_head from
+   <linux>/include/linux/types.h .  */
+
+class lk_list
+{
+  template<class T>
+  class base_iterator
+  : public std::iterator<std::bidirectional_iterator_tag, T>
+  {
+  public:
+    base_iterator (const base_iterator<T> &it) = default;
+    base_iterator (CORE_ADDR start, CORE_ADDR offset, bool embedded)
+      : m_current (start), m_start (start), m_offset (offset)
+    {
+      if (!embedded)
+	next ();
+    }
+
+    base_iterator<T> &operator++ ()
+    { return next (); }
+
+    base_iterator<T> operator++ (int)
+    { base_iterator<T> retval = *this; ++(*this); return retval; }
+
+    base_iterator<T> &operator-- ()
+    { return next (false); }
+
+    base_iterator<T> operator-- (int)
+    { base_iterator<T> retval = *this; --(*this); return retval; }
+
+    bool operator== (base_iterator<T> &other) const
+    { return (m_start == other.m_start && m_current == other.m_current
+	      && !m_first); }
+
+    bool operator!= (base_iterator<T> &other) const
+    { return !(*this == other); }
+
+    /* Return container of the list_head.  */
+    T operator* () const
+    { return m_current - m_offset; }
+
+  private:
+    /* The list_head we are currently at.  */
+    CORE_ADDR m_current;
+
+    /* First element of the list.  */
+    CORE_ADDR m_start;
+
+    /* Offset of the list_head in the containing struct.  */
+    CORE_ADDR m_offset;
+
+    /* For doubly linked lists start == end.  Use m_first to track if we
+       just started.  */
+    bool m_first = true;
+
+    /* Go to the next (forward) or prev (!forward) element.  */
+    base_iterator<T> &next (bool forward = true);
+
+    /* We must always assume that the data we handle is corrupted.  Use
+       curr->next->prev == curr (or ->prev->next if goining back).  */
+    bool is_valid_next (CORE_ADDR next, bool forward) const;
+  }; /* class base_iterator  */
+
+public:
+  /* Constructor for lists starting at address START.  */
+  inline lk_list (CORE_ADDR start, const std::string &alias,
+		  bool embedded = true);
+
+  /* Constructor for lists starting at variable NAME.  */
+  inline lk_list (const std::string &name, const std::string &alias)
+    : lk_list (lk_address (name), alias, is_embedded (name))
+  {}
+
+  typedef base_iterator<CORE_ADDR> iterator;
+  typedef base_iterator<const CORE_ADDR> const_iterator;
+
+  /* Never advance to next element for end () --> embedded = true.  */
+  iterator begin () { return iterator (m_start, m_offset, m_embedded); }
+  iterator end () { return iterator (m_start, m_offset, true); }
+
+  const_iterator cbegin () const
+  { return const_iterator (m_start, m_offset, m_embedded); }
+  const_iterator cend () const
+  { return const_iterator (m_start, m_offset, true); }
+
+  const_iterator begin () const
+  { return this->cbegin (); }
+  const_iterator end () const
+  { return this->cend (); }
+
+private:
+  /* First element of the list.  */
+  CORE_ADDR m_start;
+
+  /* Offset of the list_head in the containing struct.  */
+  CORE_ADDR m_offset;
+
+  /* Is the first list_head embedded in the containing struct, i.e. do we
+     have to consider m_start as a full element of the list or just an entry
+     point?  */
+  bool m_embedded;
+
+  /* Check whether variable name is embeded, i.e. is not a list_head.  */
+  inline bool is_embedded (const std::string &name) const;
+}; /* class lk_list */
+
+/* see declaration.  */
+
+lk_list::lk_list (CORE_ADDR start, const std::string &alias, bool embedded)
+  : m_offset (lk_offset (alias)), m_embedded (embedded)
+{
+  m_start = start;
+  if (m_embedded)
+    m_start += m_offset;
+}
+
+/* see declaration.  */
+
+bool
+lk_list::is_embedded (const std::string &name) const
+{
+  symbol *sym = lookup_symbol (name.c_str (), NULL, VAR_DOMAIN, NULL).symbol;
+  type *type = SYMBOL_TYPE (sym);
+
+  return !(TYPE_CODE (type) == TYPE_CODE_STRUCT
+	   && streq ("list_head", TYPE_TAG_NAME (type)));
+}
+
+/* see declaration.  */
+
+template<class T>
+bool
+lk_list::base_iterator<T>::is_valid_next (CORE_ADDR next, bool forward) const
+{
+  if (forward)
+    next += lk_offset ("list_head->prev");
+  else
+    next += lk_offset ("list_head->next");
+
+  return m_current == lk_read_addr (next);
+}
+
+/* see declaration.  */
+
+template<class T>
+lk_list::base_iterator<T> &
+lk_list::base_iterator<T>::next (bool forward)
+{
+  CORE_ADDR next;
+
+  if (m_current == m_start && !m_first)
+    return *this;
+
+  m_first = false;
+
+  if (forward)
+    next = lk_read_addr (m_current + lk_offset ("list_head->next"));
+  else
+    next = lk_read_addr (m_current + lk_offset ("list_head->prev"));
+
+  if (!is_valid_next (next, forward))
+    {
+      error (_("Memory corruption detected while iterating list_head at "
+	       "0x%s: list_head->%s != list_head."),
+	     phex (m_current, lk_builtin_type_size (unsigned_long)),
+	     forward ? "next->prev" : "prev->next");
+    }
+
+  m_current = next;
+
+  return *this;
+}
+#endif /* __LK_LIST_H__ */
diff --git a/gdb/lk-low.c b/gdb/lk-low.c
new file mode 100644
index 0000000000..193619e6f5
--- /dev/null
+++ b/gdb/lk-low.c
@@ -0,0 +1,864 @@ 
+/* Basic Linux kernel support, architecture independent.
+
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 3 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.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "defs.h"
+
+#include "block.h"
+#include "exceptions.h"
+#include "frame.h"
+#include "gdbarch.h"
+#include "gdbcore.h"
+#include "gdbthread.h"
+#include "gdbtypes.h"
+#include "inferior.h"
+#include "lk-bitmap.h"
+#include "lk-list.h"
+#include "lk-low.h"
+#include "objfiles.h"
+#include "observer.h"
+#include "solib.h"
+#include "target.h"
+#include "value.h"
+
+#include <algorithm>
+
+target_ops *lk_target_ops = NULL;
+linux_kernel_ops *lk_ops = NULL;
+
+/* Helper function for declare_address.  Returns address of variable NAME on
+   success or -1 on failure.  */
+
+static CORE_ADDR
+lk_find_address (const std::string &name)
+{
+  bound_minimal_symbol bmsym = lookup_minimal_symbol (name.c_str (), NULL,
+						      NULL);
+  if (bmsym.minsym == NULL)
+    return -1;
+
+  return BMSYMBOL_VALUE_ADDRESS (bmsym);
+}
+
+/* See lk-low.h.  */
+
+bool
+linux_kernel_ops::try_declare_address (const std::string &alias,
+				       const std::string &name)
+{
+  if (has_address (alias))
+    return true;
+
+  CORE_ADDR addr = lk_find_address (name);
+  if (addr == -1)
+    return false;
+
+  m_symbols[alias].addr = addr;
+  return true;
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::declare_address (const std::string &alias,
+				   const std::string &name,
+				   const lk_kconfig config)
+{
+  if (!try_declare_address (alias, name))
+    {
+      m_kconfig |= config;
+      warning (_("Missing address: %s"), alias.c_str ());
+    }
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::declare_address (const std::string &alias,
+				   const std::initializer_list<const std::string> names,
+				   const lk_kconfig config)
+{
+  for (auto &name: names)
+    if (try_declare_address (alias, name))
+      break;
+
+  if (!has_address (alias))
+    {
+      m_kconfig |= config;
+      warning (_("Missing address: %s"), alias.c_str ());
+    }
+}
+
+/* Helper function for try_declare_type.  Returns type on success or NULL on
+   failure  */
+
+static struct type *
+lk_find_type (const std::string &name)
+{
+  const struct block *global;
+  const struct symbol *sym;
+
+  global = block_global_block(get_selected_block (0));
+  sym = lookup_symbol (name.c_str (), global, STRUCT_DOMAIN, NULL).symbol;
+  if (sym != NULL)
+    return SYMBOL_TYPE (sym);
+
+  /*  Chek for "typedef struct { ... } name;"-like definitions.  */
+  sym = lookup_symbol (name.c_str (), global, VAR_DOMAIN, NULL).symbol;
+  if (sym == NULL)
+    return NULL;
+
+  struct type *type = check_typedef (SYMBOL_TYPE (sym));
+  if (TYPE_CODE (type) != TYPE_CODE_STRUCT)
+    return NULL;
+
+  return type;
+}
+
+/* See lk-low.h.  */
+
+bool
+linux_kernel_ops::try_declare_type (const std::string &alias,
+				    const std::string &name)
+{
+  if (has_type (alias))
+    return true;
+
+  struct type *type = lk_find_type (name);
+
+  if (type == NULL)
+    return false;
+
+  m_symbols[unique_type_alias (alias)].type = type;
+
+  /* Also add an entry with the name actually used to m_symbol.  Needed to
+     support chained field lookup.  */
+  if (alias != name)
+    m_symbols[unique_type_alias (name)].type = type;
+
+  return true;
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::declare_type (const std::string &alias,
+				const std::string &name,
+				const lk_kconfig config)
+{
+  if (!try_declare_type (alias, name))
+    {
+      m_kconfig |= config;
+      warning (_("Missing type: %s"), unique_type_alias (alias).c_str ());
+    }
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::declare_type (const std::string &alias,
+				const std::initializer_list<const std::string> names,
+				const lk_kconfig config)
+{
+  for (auto &name: names)
+    if (try_declare_type (alias, name))
+      break;
+
+  if (!has_type (alias))
+    {
+      m_kconfig |= config;
+      warning (_("Missing type: %s"), unique_type_alias (alias).c_str ());
+    }
+}
+
+/* Helper function for try_declare_field.  Returns lk_symbol with field
+   belonging to TYPE on success or empty on failure.  */
+
+static lk_symbol
+lk_find_field (const std::string &f_name, const struct type *type)
+{
+  struct field *field = TYPE_FIELDS (type);
+  struct field *last = field + TYPE_NFIELDS (type);
+
+  while (field != last)
+    {
+      if (streq (field->name, f_name.c_str ()))
+	return lk_symbol (field, FIELD_BYTEPOS (field));
+
+      /* Check if field is defined in anonymous struct within TYPE.  */
+      if (streq (field->name, ""))
+	{
+	  lk_symbol sym = lk_find_field (f_name, FIELD_TYPE (*field));
+	  if (sym.field != NULL)
+	    return lk_symbol (sym.field, FIELD_BYTEPOS (field) + sym.offset);
+	}
+      field++;
+    }
+  return lk_symbol ();
+}
+
+/* Helper class to parse C-like field names (type->field1->field2->...) and
+   generate aliases used in lk_ops->m_symbols.  */
+
+class lk_field_parser
+{
+public:
+  lk_field_parser (const std::string &alias)
+    : m_alias (alias)
+  {
+    /* The alias must begin with s_name->f_name of the first field.  */
+    m_end = m_alias.find (delim);
+    gdb_assert (m_end != std::string::npos);
+    m_end = m_alias.find (delim, m_end + delim.size ());
+  }
+
+  /* Return the struct, i.e. type name of the current field.  */
+  std::string s_name () const
+    {
+      if (m_last_type == NULL)
+	return m_alias.substr (0, m_alias.find (delim));
+
+      if (TYPE_CODE (m_last_type) == TYPE_CODE_TYPEDEF)
+	return TYPE_NAME (m_last_type);
+      else
+	return TYPE_TAG_NAME (m_last_type);
+    }
+
+  /* Return the field name of the current field.  */
+  std::string f_name () const
+    {
+      size_t start;
+
+      if (m_last_type == NULL)
+	start = m_alias.find (delim) + delim.size ();
+      else
+	start = m_start;
+
+      return m_alias.substr (start, m_end - start);
+    }
+
+  /* Return the full name of the current field.  */
+  std::string name () const
+  { return s_name () + delim + f_name (); }
+
+  /* Advance to the next field.  */
+  lk_field_parser *next ()
+    {
+      gdb_assert (!empty ());
+
+      m_last_type = FIELD_TYPE (*lk_field (name ()));
+      m_start = m_end + delim.size ();
+      m_end = m_alias.find (delim, m_start);
+
+      return this;
+    }
+
+  /* True when all fiels have been parsed.  */
+  bool empty () const
+  { return m_end == std::string::npos; }
+
+  /* Return the depth, i.e. number of fields, in m_alias.  */
+  unsigned int depth () const
+  {
+    size_t pos = m_alias.find (delim);
+    unsigned int ret = 0;
+
+    while (pos != std::string::npos)
+      {
+	ret ++;
+	pos = m_alias.find (delim, pos + delim.size ());
+      }
+
+    return ret;
+  }
+
+private:
+  /* Alias originally passed to parser.  */
+  std::string m_alias;
+
+  /* First index of current field in m_alias.  */
+  size_t m_start = 0;
+
+  /* Last index of current field in m_alias.  */
+  size_t m_end = 0;
+
+  /* Type of the last field found.  Needed to get s_name of embedded
+     fields.  */
+  struct type *m_last_type = NULL;
+
+  /* Delemiter used to separate fields.  */
+  const std::string delim = "->";
+};
+
+/* See lk-low.h.  */
+
+bool
+linux_kernel_ops::try_declare_field (const std::string &orig_alias,
+				     const std::string &orig_name)
+{
+  if (has_field (orig_alias))
+    return true;
+
+  lk_field_parser alias (orig_alias);
+  lk_field_parser name (orig_name);
+
+  /* Only allow declaration of one field at a time.  */
+  gdb_assert (alias.depth () == 1);
+  gdb_assert (name.depth () == 1);
+
+  if (!try_declare_type (alias.s_name (), name.s_name ()))
+    return false;
+
+  lk_symbol field = lk_find_field (name.f_name (), type (alias.s_name ()));
+  if (field.field == NULL)
+    return false;
+
+  m_symbols[alias.name ()] = field;
+  return true;
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::declare_field (const std::string &alias,
+				 const std::string &name,
+				 const lk_kconfig config)
+{
+  if (!try_declare_field (alias, name))
+    {
+      m_kconfig |= config;
+      warning (_("Missing field: %s"), alias.c_str ());
+    }
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::declare_field (const std::string &alias,
+				 const std::initializer_list<const std::string> names,
+				 const lk_kconfig config)
+{
+  for (auto &name: names)
+    if (try_declare_field (alias, name))
+      break;
+
+  if (!has_field (alias))
+    {
+      m_kconfig |= config;
+      warning (_("Missing field: %s"), alias.c_str ());
+    }
+}
+
+/* See lk-low.h.  */
+
+void
+linux_kernel_ops::read_symbols ()
+{
+  if (!m_symbols.empty ())
+    m_symbols.clear ();
+
+  declare_field ("task_struct->tasks", LK_CONFIG_ALWAYS);
+  declare_field ("task_struct->pid", LK_CONFIG_ALWAYS);
+  declare_field ("task_struct->tgid", LK_CONFIG_ALWAYS);
+  declare_field ("task_struct->thread_group", LK_CONFIG_ALWAYS);
+  declare_field ("task_struct->comm", LK_CONFIG_ALWAYS);
+  declare_field ("task_struct->thread", LK_CONFIG_ALWAYS);
+
+  declare_field ("list_head->next", LK_CONFIG_ALWAYS);
+  declare_field ("list_head->prev", LK_CONFIG_ALWAYS);
+
+  declare_field ("rq->curr", LK_CONFIG_ALWAYS);
+
+  declare_field ("cpumask->bits", LK_CONFIG_ALWAYS);
+
+  declare_address ("init_task", LK_CONFIG_ALWAYS);
+  declare_address ("runqueues", LK_CONFIG_ALWAYS);
+  declare_address ("__per_cpu_offset", LK_CONFIG_ALWAYS);
+
+  declare_address ("cpu_online_mask", {"__cpu_online_mask", /* linux 4.5+ */
+				       "cpu_online_bits"},  /* linux -4.4 */
+		   LK_CONFIG_ALWAYS);
+
+  arch_read_symbols ();
+
+  if (!ifdef (LK_CONFIG_ALWAYS))
+    error (_("Could not find all symbols needed.  Aborting."));
+}
+
+/* See lk-low.h.  */
+
+CORE_ADDR
+linux_kernel_ops::offset (const std::string &orig_alias) const
+{
+  lk_field_parser alias (orig_alias);
+  CORE_ADDR ret = m_symbols.at (alias.name ()).offset;
+
+  while (!alias.empty ())
+    ret += m_symbols.at (alias.next ()->name ()).offset;
+
+  return ret;
+}
+
+/* Map cpu number CPU to the original PTID from target beneath.  */
+
+static ptid_t
+lk_cpu_to_old_ptid (const unsigned int cpu)
+{
+  struct lk_ptid_map *ptid_map;
+
+  for (ptid_map = lk_ops->old_ptid; ptid_map;
+       ptid_map = ptid_map->next)
+    {
+      if (ptid_map->cpu == cpu)
+	return ptid_map->old_ptid;
+    }
+
+  error (_("Could not map CPU %d to original PTID.  Aborting."), cpu);
+}
+
+/* Helper functions to read and return basic types at a given ADDRess.  */
+
+/* Read and return the integer value at address ADDR.  */
+
+int
+lk_read_int (CORE_ADDR addr)
+{
+  size_t int_size = lk_builtin_type_size (int);
+  enum bfd_endian endian = gdbarch_byte_order (current_inferior ()->gdbarch);
+  return read_memory_integer (addr, int_size, endian);
+}
+
+/* Read and return the unsigned integer value at address ADDR.  */
+
+unsigned int
+lk_read_uint (CORE_ADDR addr)
+{
+  size_t uint_size = lk_builtin_type_size (unsigned_int);
+  enum bfd_endian endian = gdbarch_byte_order (current_inferior ()->gdbarch);
+  return read_memory_unsigned_integer (addr, uint_size, endian);
+}
+
+/* Read and return the long integer value at address ADDR.  */
+
+LONGEST
+lk_read_long (CORE_ADDR addr)
+{
+  size_t long_size = lk_builtin_type_size (long);
+  enum bfd_endian endian = gdbarch_byte_order (current_inferior ()->gdbarch);
+  return read_memory_integer (addr, long_size, endian);
+}
+
+/* Read and return the unsigned long integer value at address ADDR.  */
+
+ULONGEST
+lk_read_ulong (CORE_ADDR addr)
+{
+  size_t ulong_size = lk_builtin_type_size (unsigned_long);
+  enum bfd_endian endian = gdbarch_byte_order (current_inferior ()->gdbarch);
+  return read_memory_unsigned_integer (addr, ulong_size, endian);
+}
+
+/* Read and return the address value at address ADDR.  */
+
+CORE_ADDR
+lk_read_addr (CORE_ADDR addr)
+{
+  return (CORE_ADDR) lk_read_ulong (addr);
+}
+
+/* See lk-low.h.  */
+
+CORE_ADDR
+linux_kernel_ops::percpu_offset (unsigned int cpu)
+{
+  size_t ulong_size = lk_builtin_type_size (unsigned_long);
+  CORE_ADDR percpu_elt = address ("__per_cpu_offset") + (ulong_size * cpu);
+  return lk_read_addr (percpu_elt);
+}
+
+/* See lk-low.h.  */
+
+unsigned int
+linux_kernel_ops::beneath_thread_to_cpu (thread_info *ti)
+{
+  for (unsigned int cpu : lk_cpu_online_mask)
+    {
+      CORE_ADDR rq = address ("runqueues") + percpu_offset (cpu);
+      CORE_ADDR curr = lk_read_addr (rq + offset ("rq->curr"));
+      int pid = lk_read_int (curr + offset ("task_struct->pid"));
+
+      if (pid == ti->ptid.lwp ())
+	return cpu;
+    }
+
+  error (_("Could not map thread with pid %d, lwp %lu to a cpu."),
+	 ti->ptid.pid (), ti->ptid.lwp ());
+}
+
+/* Test if a given task TASK is running.  See comment in lk-low.h for
+   details.  */
+
+unsigned int
+lk_task_running (CORE_ADDR task)
+{
+  for (unsigned int cpu : lk_cpu_online_mask)
+    {
+      CORE_ADDR rq = lk_address ("runqueues") + lk_ops->percpu_offset (cpu);
+      CORE_ADDR curr = lk_read_addr (rq + lk_offset ("rq->curr"));
+
+      if (curr == task)
+	return cpu;
+    }
+
+  return LK_CPU_INVAL;
+}
+
+/* Update running tasks with information from struct rq->curr. */
+
+static void
+lk_update_running_tasks ()
+{
+  for (unsigned int cpu : lk_cpu_online_mask)
+    {
+      CORE_ADDR rq = lk_address ("runqueues") + lk_ops->percpu_offset (cpu);
+      CORE_ADDR curr = lk_read_addr (rq + lk_offset ("rq->curr"));
+      int pid = lk_read_int (curr + lk_offset ("task_struct->pid"));
+      int inf_pid = current_inferior ()->pid;
+
+      ptid_t new_ptid (inf_pid, pid, curr);
+      ptid_t old_ptid = lk_cpu_to_old_ptid (cpu); /* FIXME not suitable for
+						     running targets? */
+
+      thread_info *tp = find_thread_ptid (old_ptid);
+      if (tp && tp->state != THREAD_EXITED)
+	thread_change_ptid (old_ptid, new_ptid);
+    }
+}
+
+/* Update sleeping tasks by walking the task_structs starting from
+   init_task.  */
+
+static void
+lk_update_sleeping_tasks ()
+{
+  int inf_pid = current_inferior ()->pid;
+
+  for (CORE_ADDR task : lk_list ("init_task", "task_struct->tasks"))
+    {
+      for (CORE_ADDR thread : lk_list (task, "task_struct->thread_group"))
+	{
+	  int pid = lk_read_int (thread + lk_offset ("task_struct->pid"));
+	  ptid_t ptid (inf_pid, pid, thread);
+
+	  thread_info *tp = find_thread_ptid (ptid);
+	  if (tp == NULL || tp->state == THREAD_EXITED)
+	    add_thread (ptid);
+	}
+    }
+}
+
+/* Function for targets to_update_thread_list hook.  */
+
+static void
+lk_update_thread_list (struct target_ops *target)
+{
+  prune_threads ();
+  lk_update_running_tasks ();
+  lk_update_sleeping_tasks ();
+}
+
+/* Function for targets to_fetch_registers hook.  */
+
+static void
+lk_fetch_registers (struct target_ops *target,
+		    struct regcache *regcache, int regnum)
+{
+  CORE_ADDR task = (CORE_ADDR) regcache->ptid ().tid ();
+
+  /* Are we called during init?  */
+  if (task == 0)
+    return target->beneath->to_fetch_registers (target, regcache, regnum);
+
+  unsigned int cpu = lk_task_running (task);
+
+  /* Let the target beneath fetch registers of running tasks.  */
+  if (cpu != LK_CPU_INVAL)
+    {
+      scoped_restore_regcache_ptid restore_regcache (regcache);
+      regcache->set_ptid (lk_cpu_to_old_ptid (cpu));
+
+      lk_ops->beneath ()->to_fetch_registers (target, regcache, regnum);
+    }
+  else
+    {
+      lk_ops->get_registers (task, target, regcache, regnum);
+
+      /* Mark all registers not found as unavailable.  */
+      for (int i = 0; i < gdbarch_num_regs (regcache->arch ()); i++)
+	{
+	  if (regcache->get_register_status (i) != REG_VALID)
+	    regcache->invalidate (i);
+	}
+    }
+}
+
+/* Function for targets to_pid_to_str hook.  Marks running tasks with an
+   asterisk "*".  */
+
+static const char *
+lk_pid_to_str (struct target_ops *target, ptid_t ptid)
+{
+  CORE_ADDR task = (CORE_ADDR) ptid.tid ();
+  static std::string str;
+  const char *fmt;
+
+  if (lk_task_running (task) != LK_CPU_INVAL)
+    fmt = "PID: %5li*, 0x%s";
+  else
+    fmt = "PID: %6li, 0x%s";
+
+  str = string_printf (fmt, ptid.lwp (),
+		       phex (task, lk_builtin_type_size (unsigned_long)));
+
+  return str.c_str ();
+}
+
+/* Function for targets to_thread_name hook.  */
+
+static const char *
+lk_thread_name (struct target_ops *target, struct thread_info *ti)
+{
+  static std::string str (LK_TASK_COMM_LEN, '\0');
+
+  size_t size = std::min ((unsigned int) LK_TASK_COMM_LEN,
+			  LK_ARRAY_LEN(lk_field ("task_struct->comm")));
+
+  CORE_ADDR task = (CORE_ADDR) ti->ptid.tid ();
+  CORE_ADDR comm = task + lk_offset ("task_struct->comm");
+  read_memory (comm, (gdb_byte *) str.data (), size);
+
+  str = string_printf ("%-16s", str.c_str ());
+
+  return str.c_str ();
+}
+
+
+/* Functions to initialize and free target_ops and its private data.  As well
+   as functions for targets to_open/close/detach hooks.  */
+
+/* Check if OBFFILE is a Linux kernel.  */
+
+static bool
+lk_is_linux_kernel (struct objfile *objfile)
+{
+  int ok = 0;
+
+  if (objfile == NULL || !(objfile->flags & OBJF_MAINLINE))
+    return false;
+
+  ok += lookup_minimal_symbol ("linux_banner", NULL, objfile).minsym != NULL;
+  ok += lookup_minimal_symbol ("_stext", NULL, objfile).minsym != NULL;
+  ok += lookup_minimal_symbol ("_etext", NULL, objfile).minsym != NULL;
+
+  return (ok > 2);
+}
+
+/* Frees the cpu to old ptid map.  */
+
+static void
+lk_free_ptid_map ()
+{
+  while (lk_ops->old_ptid)
+    {
+      struct lk_ptid_map *tmp;
+
+      tmp = lk_ops->old_ptid;
+      lk_ops->old_ptid = tmp->next;
+      XDELETE (tmp);
+    }
+}
+
+/* Initialize the cpu to old ptid map.  */
+
+static void
+lk_init_ptid_map ()
+{
+  struct thread_info *ti;
+
+  if (lk_ops->old_ptid != NULL)
+    lk_free_ptid_map ();
+
+  ALL_THREADS (ti)
+    {
+      struct lk_ptid_map *ptid_map = XCNEW (struct lk_ptid_map);
+
+      ptid_map->cpu = lk_ops->beneath_thread_to_cpu (ti);
+      ptid_map->old_ptid = ti->ptid;
+
+      ptid_map->next = lk_ops->old_ptid;
+      lk_ops->old_ptid = ptid_map;
+    }
+}
+
+/* Initializes all private data and pushes the linux kernel target, if not
+   already done.  */
+
+static void
+lk_try_push_target ()
+{
+  struct gdbarch *gdbarch;
+
+  gdbarch = current_inferior ()->gdbarch;
+  if (!(gdbarch && gdbarch_get_new_lk_ops_p (gdbarch)))
+    error (_("Linux kernel debugging not supported on %s."),
+	   gdbarch_bfd_arch_info (gdbarch)->printable_name);
+
+  lk_ops = gdbarch_get_new_lk_ops (gdbarch, lk_target_ops);
+  lk_ops->read_symbols ();
+
+  lk_init_ptid_map ();
+  lk_update_thread_list (lk_ops->target ());
+
+  if (!target_is_pushed (lk_target_ops))
+    push_target (lk_ops->target ());
+}
+
+/* Function for targets to_open hook.  */
+
+static void
+lk_open (const char *args, int from_tty)
+{
+  struct objfile *objfile;
+
+  if (target_is_pushed (lk_target_ops))
+    {
+      printf_unfiltered (_("Linux kernel target already pushed.  Aborting\n"));
+      return;
+    }
+
+  for (objfile = current_program_space->objfiles; objfile;
+       objfile = objfile->next)
+    {
+      if (lk_is_linux_kernel (objfile)
+	  && inferior_ptid.pid () != 0)
+	{
+	  lk_try_push_target ();
+	  return;
+	}
+    }
+  printf_unfiltered (_("Could not find a valid Linux kernel object file.  "
+		       "Aborting.\n"));
+}
+
+/* Function for targets to_close hook.  Deletes all private data.  */
+
+static void
+lk_close (struct target_ops *ops)
+{
+  lk_free_ptid_map ();
+
+  delete (lk_ops);
+}
+
+/* Function for targets to_detach hook.  */
+
+static void
+lk_detach (struct target_ops *t, inferior *inf, int from_tty)
+{
+  struct target_ops *beneath = lk_ops->beneath ();
+
+  unpush_target (lk_ops->target ());
+  reinit_frame_cache ();
+  if (from_tty)
+    printf_filtered (_("Linux kernel target detached.\n"));
+
+  beneath->to_detach (beneath, inf, from_tty);
+}
+
+/* Function for new objfile observer.  */
+
+static void
+lk_observer_new_objfile (struct objfile *objfile)
+{
+  if (lk_is_linux_kernel (objfile) && inferior_ptid.pid () != 0)
+    lk_try_push_target ();
+}
+
+/* Function for inferior created observer.  */
+
+static void
+lk_observer_inferior_created (struct target_ops *ops, int from_tty)
+{
+  struct objfile *objfile;
+
+  if (inferior_ptid.pid () == 0)
+    return;
+
+  for (objfile = current_inferior ()->pspace->objfiles; objfile;
+       objfile = objfile->next)
+    {
+      if (lk_is_linux_kernel (objfile))
+	{
+	  lk_try_push_target ();
+	  return;
+	}
+    }
+}
+
+/* Initialize linux kernel target.  */
+
+static void
+init_lk_target_ops (void)
+{
+  struct target_ops *t;
+
+  if (lk_target_ops != NULL)
+    return;
+
+  t = XCNEW (struct target_ops);
+  t->to_shortname = "linux-kernel";
+  t->to_longname = "linux kernel support";
+  t->to_doc = "Adds support to debug the Linux kernel";
+
+  t->to_open = lk_open;
+  t->to_close = lk_close;
+  t->to_detach = lk_detach;
+  t->to_fetch_registers = lk_fetch_registers;
+  t->to_update_thread_list = lk_update_thread_list;
+  t->to_pid_to_str = lk_pid_to_str;
+  t->to_thread_name = lk_thread_name;
+
+  t->to_stratum = thread_stratum;
+  t->to_magic = OPS_MAGIC;
+
+  lk_target_ops = t;
+
+  add_target (t);
+}
+
+/* Provide a prototype to silence -Wmissing-prototypes.  */
+extern initialize_file_ftype _initialize_linux_kernel;
+
+void
+_initialize_linux_kernel (void)
+{
+  init_lk_target_ops ();
+
+  observer_attach_new_objfile (lk_observer_new_objfile);
+  observer_attach_inferior_created (lk_observer_inferior_created);
+}
diff --git a/gdb/lk-low.h b/gdb/lk-low.h
new file mode 100644
index 0000000000..39c0d88f43
--- /dev/null
+++ b/gdb/lk-low.h
@@ -0,0 +1,335 @@ 
+/* Basic Linux kernel support, architecture independent.
+
+   Copyright (C) 2016 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 3 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.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef __LK_LOW_H__
+#define __LK_LOW_H__
+
+#include <unordered_map>
+
+#include "gdbtypes.h"
+#include "target.h"
+
+/* Copied constants defined in Linux kernel.  */
+#define LK_TASK_COMM_LEN 16
+#define LK_BITS_PER_BYTE 8
+
+/* Definitions used in linux kernel target.  */
+#define LK_CPU_INVAL -1U
+
+/* Helper functions to read and return a value at a given ADDRess.  */
+extern int lk_read_int (CORE_ADDR addr);
+extern unsigned int lk_read_uint (CORE_ADDR addr);
+extern LONGEST lk_read_long (CORE_ADDR addr);
+extern ULONGEST lk_read_ulong (CORE_ADDR addr);
+extern CORE_ADDR lk_read_addr (CORE_ADDR addr);
+
+/* Enum to track the config options used to build the kernel.  Whenever
+   a symbol is declared (in linux_kernel_ops::{arch_}read_symbols) which
+   only exists if the kernel was built with a certain config option an entry
+   has to be added here.  */
+enum lk_kconfig_values
+{
+  LK_CONFIG_ALWAYS	= 1 << 0,
+  LK_CONFIG_SMT		= 1 << 1,
+  LK_CONFIG_MODULES	= 1 << 2,
+};
+DEF_ENUM_FLAGS_TYPE (enum lk_kconfig_values, lk_kconfig);
+
+/* We use the following convention for PTIDs:
+
+   ptid->pid = inferiors PID
+   ptid->lwp = PID from task_stuct
+   ptid->tid = address of task_struct
+
+   The task_structs address as TID has two reasons.  First, we need it quite
+   often and there is no other reasonable way to pass it down.  Second, it
+   helps us to distinguish swapper tasks as they all have PID = 0.
+
+   Furthermore we cannot rely on the target beneath to use the same PID as the
+   task_struct. Thus we need a mapping between our PTID and the PTID of the
+   target beneath. Otherwise it is impossible to pass jobs, e.g. fetching
+   registers of running tasks, to the target beneath.  */
+
+/* Private data struct to map between our and the target beneath PTID.  */
+
+struct lk_ptid_map
+{
+  struct lk_ptid_map *next;
+  unsigned int cpu;
+  ptid_t old_ptid;
+};
+
+/* Cache for the value of a symbol.  Used in linux_kernel_ops->m_symbols.  */
+
+union lk_symbol
+{
+  CORE_ADDR addr;
+  struct type *type;
+  struct
+  {
+    struct field *field;
+    CORE_ADDR offset;
+  };
+
+  lk_symbol () {field = NULL; offset = 0;}
+  lk_symbol (struct field *f, CORE_ADDR o) {field = f, offset = o;}
+};
+
+class linux_kernel_ops
+{
+public:
+  linux_kernel_ops (struct target_ops *ops)
+    : m_ops (ops)
+  {}
+
+  virtual ~linux_kernel_ops () = default;
+
+  /* Read registers from the target and supply their content to regcache.  */
+  virtual void get_registers (CORE_ADDR task, struct target_ops *target,
+			      struct regcache *regcache, int regnum) = 0;
+
+  /* Return the per_cpu_offset of cpu CPU.  Default uses __per_cpu_offset
+     array to determine the offset.  */
+  virtual CORE_ADDR percpu_offset (unsigned int cpu);
+
+  /* Maps thread of target beneath to a cpu id.  Default assumes
+     rq->curr->pid == beneath_ptid.lwp.  */
+  virtual unsigned int beneath_thread_to_cpu (thread_info *ti);
+
+  /* Return the linux-kernel target.  */
+  struct target_ops *target () const
+  { return m_ops; }
+
+  /* Return the target beneath the linux-kernel target.  */
+  struct target_ops *beneath () const
+  { return m_ops->beneath; }
+
+  /* Return a previously declared address with key ALIAS.
+     Throws internal_error if requested symbol was not declared first.  */
+  CORE_ADDR address (const std::string &alias) const
+  {
+    gdb_assert (has_address (alias));
+    return m_symbols.at (alias).addr;
+  }
+
+  /* Same like address but for types.  */
+  struct type *type (const std::string &alias) const
+  {
+    gdb_assert (has_type (alias));
+    return m_symbols.at (unique_type_alias(alias)).type;
+  }
+
+  /* Same like address but for fields.  */
+  struct field *field (const std::string &alias) const
+  {
+    gdb_assert (has_field (alias));
+    return m_symbols.at (alias).field;
+  }
+
+  /* Checks whether address ALIAS exists in m_symbols.  */
+  bool has_address (const std::string &alias) const
+  { return has_symbol (alias); }
+
+  /* Same like has_address but for types.  */
+  bool has_type (const std::string &alias) const
+  { return has_symbol (unique_type_alias (alias)); }
+
+  /* Same like has_address but for fields.  */
+  bool has_field (const std::string &alias) const
+  { return has_symbol (alias); }
+
+  /* Return offset of field ALIAS (in byte).  */
+  CORE_ADDR offset (const std::string &alias) const;
+
+  /* Check whether the kernel was build using this config option.  */
+  bool ifdef (lk_kconfig config) const
+  { return !(m_kconfig & config); }
+
+  /* Linked list to map between cpu number and original ptid from target
+     beneath.  */
+  struct lk_ptid_map *old_ptid = NULL;
+
+  /* Declare and initialize all symbols needed.  Must be called _after_
+     symbol tables were initialized.  */
+  void read_symbols ();
+
+protected:
+  /* Virtual method to allow architectures to declare their own symbols.
+     Called by read_symbols.  */
+  virtual void arch_read_symbols ()
+  {}
+
+  /* Helper function to declare_address.  Returns true when symbol NAME
+     using key ALIAS was successfully declared, false otherwise.  Try not to
+     use this function directly but use declare_address instead.  */
+  bool try_declare_address (const std::string &alias,
+			    const std::string &names);
+
+  /* Same like try_declare_address but for types.  */
+  bool try_declare_type (const std::string &alias, const std::string &name);
+
+  /* Same like try_declare_address but for fields.  */
+  bool try_declare_field (const std::string &alias, const std::string &name);
+
+  /* Same like try_declare_field but with NAME = ALIAS.  */
+  bool try_declare_field (const std::string &name)
+  { return try_declare_field (name, name); }
+
+  /* Declare symbol NAME using key ALIAS.  If no symbol NAME could be found
+     mark CONFIG as missing.  */
+  void declare_address (const std::string &alias, const std::string &name,
+			const lk_kconfig config);
+
+  /* Same like above but with NAME = ALIAS.  */
+  void declare_address (const std::string &name, const lk_kconfig config)
+  { declare_address (name, name, config); }
+
+  /* Same like above but only mark CONFIG as missing if none of the symbols
+     in NAMES could be found.  */
+  void declare_address (const std::string &alias,
+			const std::initializer_list<const std::string> names,
+			const lk_kconfig config);
+
+  /* See declare_address.  */
+  void declare_type (const std::string &alias, const std::string &name,
+		     const lk_kconfig config);
+
+  /* See declare_address.  */
+  void declare_type (const std::string &name, const lk_kconfig config)
+  { declare_type (name, name, config); }
+
+  /* See declare_address.  */
+  void declare_type (const std::string &alias,
+		     const std::initializer_list<const std::string> names,
+		     const lk_kconfig config);
+
+  /* See declare_address.  */
+  void declare_field (const std::string &alias, const std::string &name,
+		      const lk_kconfig kconfig);
+
+  /* See declare_address.  */
+  void declare_field (const std::string &name, const lk_kconfig kconfig)
+  { declare_field (name, name, kconfig); }
+
+  /* See declare_address.  */
+  void declare_field (const std::string &alias,
+		      const std::initializer_list <const std::string> names,
+		      const lk_kconfig config);
+
+private:
+  /* The target_ops we are working with.  */
+  struct target_ops *m_ops;
+
+  /* The configuration used to build the kernel.  To make the implementation
+     easier m_kconfig is inverse, i.e. it tracks the _missing_ config options
+     not the set ones.  */
+  lk_kconfig m_kconfig = 0;
+
+  /* Collection of all declared symbols (addresses, fields etc.).  */
+  std::unordered_map<std::string, union lk_symbol> m_symbols;
+
+  /* Returns unique alias for struct ALIAS.  */
+  const std::string unique_type_alias (const std::string &alias) const
+  {
+    std::string prefix ("struct ");
+    if (startswith (alias.c_str (), prefix.c_str ()))
+	return alias;
+    return prefix + alias;
+  }
+
+  /* Check if m_symbols contains ALIAS.  */
+  bool has_symbol (const std::string &alias) const
+  { return m_symbols.count (alias) != 0; }
+};
+
+extern target_ops *lk_target_ops;
+extern linux_kernel_ops *lk_ops;
+
+/* Short hand access to frequently used lk_ops methods.  */
+
+static inline CORE_ADDR
+lk_address (const std::string &alias)
+{
+  return lk_ops->address (alias);
+}
+
+static inline struct type *
+lk_type (const std::string &alias)
+{
+  return lk_ops->type (alias);
+}
+
+static inline struct field *
+lk_field (const std::string &alias)
+{
+  return lk_ops->field (alias);
+}
+
+static inline CORE_ADDR
+lk_offset (const std::string &alias)
+{
+  return lk_ops->offset (alias);
+}
+
+static inline bool
+lk_ifdef (lk_kconfig config)
+{
+  return lk_ops->ifdef (config);
+}
+
+/* Align VAL to BASE. BASE must be a power of 2.  */
+#define LK_ALIGN(val, base) LK_ALIGN_MASK ((val), (typeof(val))(base) - 1)
+
+/* Same as LK_ALIGN, but aligns down.  */
+#define LK_ALIGN_DOWN(val, base) ((val) & ~((typeof(val))(base) - 1))
+
+/* Helper for LK_ALIGN.  */
+#define LK_ALIGN_MASK(val, mask) (((val) + (mask)) & ~(mask))
+
+/* Short hand access to current gdbarchs builtin types and their
+   size (in byte).  For TYPE replace spaces " " by underscore "_", e.g.
+   "unsigned int" => "unsigned_int".  */
+#define lk_builtin_type(type)					\
+  (builtin_type (current_inferior ()->gdbarch)->builtin_##type)
+#define lk_builtin_type_size(type)		\
+  (lk_builtin_type (type)->length)
+
+/* If field FIELD is an array returns its length (in #elements).  */
+#define LK_ARRAY_LEN(field)			\
+  (FIELD_SIZE (field) / FIELD_TARGET_SIZE (field))
+
+/* Additional access macros to fields in the style of gdbtypes.h */
+/* Returns the size of field FIELD (in bytes). If FIELD is an array returns
+   the size of the whole array.  */
+#define FIELD_SIZE(field)			\
+  TYPE_LENGTH (check_typedef (FIELD_TYPE ((*field))))
+
+/* Returns the size of the target type of field FIELD (in bytes).  If FIELD is
+   an array returns the size of its elements.  */
+#define FIELD_TARGET_SIZE(field)		\
+  TYPE_LENGTH (check_typedef (TYPE_TARGET_TYPE (FIELD_TYPE ((*field)))))
+
+#define FIELD_BYTEPOS(field)			\
+  (FIELD_BITPOS (*field) / LK_BITS_PER_BYTE)
+
+/* Tests if a given task TASK is running. Returns either the cpu-id
+   if running or LK_CPU_INVAL if not.  */
+extern unsigned int lk_task_running (CORE_ADDR task);
+
+#endif /* __LK_LOW_H__ */
diff --git a/gdb/osabi.c b/gdb/osabi.c
index fd44deb9fa..14ddd414f3 100644
--- a/gdb/osabi.c
+++ b/gdb/osabi.c
@@ -64,6 +64,7 @@  static const struct osabi_names gdb_osabi_names[] =
   { "GNU/Hurd", NULL },
   { "Solaris", NULL },
   { "GNU/Linux", "linux(-gnu[^-]*)?" },
+  { "Linux kernel", NULL },
   { "FreeBSD", NULL },
   { "NetBSD", NULL },
   { "OpenBSD", NULL },