Patchwork [RFC,v5,5/9] Add kernel module support for linux-kernel target

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

Comments

Philipp Rudo - March 12, 2018, 3:31 p.m.
Implement module support for the new linux-kernel target by handling them
as shared libraries.

Unlike everything before, modules live in a kernel virtual address space.
So the linux-kernel target has to be extended to support address
translation.  The approach to do so is to hook into to_xfer_partial and
translate the address before it is passed down to the target beneath.
Unfortunately this approach has a huge drawback, as to_xfer_partial only
gets the address as ULONGEST without any information on the address space.
So all addresses are treated as kernel virtual addresses, even when they are
from other address spaces, causing mistranslations.

gdb/ChangeLog:

    * lk-modules.h: New file.
    * lk-modules.c: New file.
    * lk-low.h <linux_kernel_ops> (is_kvaddr, vtop, kvtop)
    (adjust_module_layout): New virtual methods.
    (LK_MODULES_NAME_LEN, LK_UTS_NAME_LEN): New define.
    * lk-low.c (lk-modules.h): New include.
    (linux_kernel_ops::read_symbols): Declare needed symbols.
    (lk_xfer_partial): New functions.
    (lk_try_push_target): Set solib_ops.
    (init_linux_kernel_ops): Set to_xfer_partial.
    * solib.c (get_solib_search_path): New function.
    * solib.h (get_solib_search_path): New export.
    * Makefile.in (ALLDEPFILES): Add lk-modules.c.
    (HFILES_NO_SRCDIR): Add lk-modules.h.
    (ALL_TARGET_OBS): Add lk-modules.o.
    * configure.tgt (lk_tobjs): Add lk-modules.o.
---
 gdb/Makefile.in   |   3 +
 gdb/configure.tgt |   2 +-
 gdb/lk-low.c      |  86 ++++++++++
 gdb/lk-low.h      |  27 +++
 gdb/lk-modules.c  | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gdb/lk-modules.h  | 148 ++++++++++++++++
 gdb/solib.c       |   8 +
 gdb/solib.h       |   5 +
 8 files changed, 779 insertions(+), 1 deletion(-)
 create mode 100644 gdb/lk-modules.c
 create mode 100644 gdb/lk-modules.h

Patch

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 056333e2cd..afa8039a95 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -716,6 +716,7 @@  ALL_TARGET_OBS = \
 	linux-record.o \
 	linux-tdep.o \
 	lk-low.o \
+	lk-modules.o \
 	lm32-tdep.o \
 	m32c-tdep.o \
 	m32r-linux-tdep.o \
@@ -1278,6 +1279,7 @@  HFILES_NO_SRCDIR = \
 	linux-record.h \
 	linux-tdep.h \
 	lk-low.h \
+	lk-modules.h \
 	location.h \
 	m2-lang.h \
 	m32r-tdep.h \
@@ -2259,6 +2261,7 @@  ALLDEPFILES = \
 	linux-record.c \
 	linux-tdep.c \
 	lk-low.c \
+	lk-modules.c \
 	lm32-tdep.c \
 	m32r-linux-nat.c \
 	m32r-linux-tdep.c \
diff --git a/gdb/configure.tgt b/gdb/configure.tgt
index be68ac50fc..39b2144e00 100644
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -42,7 +42,7 @@  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"
+lk_tobjs="lk-low.o lk-modules.o"
 
 # Here are three sections to get a list of target specific object
 # files according to target triplet $TARG.
diff --git a/gdb/lk-low.c b/gdb/lk-low.c
index 193619e6f5..54d3b6272d 100644
--- a/gdb/lk-low.c
+++ b/gdb/lk-low.c
@@ -30,6 +30,7 @@ 
 #include "lk-bitmap.h"
 #include "lk-list.h"
 #include "lk-low.h"
+#include "lk-modules.h"
 #include "objfiles.h"
 #include "observer.h"
 #include "solib.h"
@@ -386,9 +387,68 @@  linux_kernel_ops::read_symbols ()
 
   declare_field ("cpumask->bits", LK_CONFIG_ALWAYS);
 
+  declare_field ("mm_struct->pgd", LK_CONFIG_ALWAYS);
+
+  declare_field ("pgd_t->pgd", LK_CONFIG_ALWAYS);
+
+  declare_field ("module->state", LK_CONFIG_MODULES);
+  declare_field ("module->list", LK_CONFIG_MODULES);
+  declare_field ("module->name", LK_CONFIG_MODULES);
+  declare_field ("module->source_list", LK_CONFIG_MODULES);
+  declare_field ("module->arch", LK_CONFIG_MODULES);
+  declare_field ("module->percpu", LK_CONFIG_SMT);
+  declare_field ("module->percpu_size", LK_CONFIG_SMT);
+
+  /* Module offset moved to new struct module_layout with linux 4.5.
+     It must be checked in code which of these fields exist.  */
+  if (try_declare_field ("module_layout->base")) /* linux 4.5+ */
+    {
+      declare_field ("module->init_layout", LK_CONFIG_MODULES);
+      declare_field ("module->core_layout", LK_CONFIG_MODULES);
+
+      declare_field ("module_layout->size", LK_CONFIG_MODULES);
+      declare_field ("module_layout->text_size", LK_CONFIG_MODULES);
+      declare_field ("module_layout->ro_size", LK_CONFIG_MODULES);
+
+      /* Only try to declare -> existence needs to be checked in code.  */
+      try_declare_field ("module_layout->ro_after_init_size"); /* linux 4.8+ */
+    }
+  else if (try_declare_field ("module->module_core")) /* linux -4.4 */
+    {
+      declare_field ("module->module_init", LK_CONFIG_MODULES);
+
+      declare_field ("module->init_size", LK_CONFIG_MODULES);
+      declare_field ("module->core_size", LK_CONFIG_MODULES);
+
+      declare_field ("module->init_text_size", LK_CONFIG_MODULES);
+      declare_field ("module->core_text_size", LK_CONFIG_MODULES);
+
+      declare_field ("module->init_ro_size", LK_CONFIG_MODULES);
+      declare_field ("module->core_ro_size", LK_CONFIG_MODULES);
+    }
+  else
+    {
+      m_kconfig |= LK_CONFIG_MODULES;
+      warning (_("No symbols for the module layout found."));
+    }
+
+  declare_field ("module_use->source_list", LK_CONFIG_MODULES);
+  declare_field ("module_use->source", LK_CONFIG_MODULES);
+
+  declare_field ("uts_namespace->name", LK_CONFIG_ALWAYS);
+
+  declare_type ("utsname",
+		{"new_utsname", "old_utsname", "oldold_utsname"},
+		LK_CONFIG_ALWAYS);
+  declare_field ("utsname->version", LK_CONFIG_ALWAYS);
+  declare_field ("utsname->release", 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 ("init_mm", LK_CONFIG_ALWAYS);
+  declare_address ("modules", LK_CONFIG_MODULES);
+  declare_address ("init_uts_ns", LK_CONFIG_ALWAYS);
 
   declare_address ("cpu_online_mask", {"__cpu_online_mask", /* linux 4.5+ */
 				       "cpu_online_bits"},  /* linux -4.4 */
@@ -657,6 +717,25 @@  lk_thread_name (struct target_ops *target, struct thread_info *ti)
   return str.c_str ();
 }
 
+/* Function for targets to_xfer_partial hook.  */
+
+enum target_xfer_status
+lk_xfer_partial (struct target_ops *ops, enum target_object object,
+		 const char *annex, gdb_byte *readbuf,
+		 const gdb_byte *writebuf, ULONGEST offset, ULONGEST len,
+		 ULONGEST *xfered_len)
+{
+  enum target_xfer_status ret_val;
+  scoped_restore restore_target = make_scoped_restore (&current_target.beneath,
+						       ops->beneath);
+  if (lk_ops->is_kvaddr (offset))
+    offset = lk_ops->kvtop (offset);
+
+  ret_val =  ops->beneath->to_xfer_partial (ops->beneath, object, annex,
+					    readbuf, writebuf, offset, len,
+					    xfered_len);
+  return ret_val;
+}
 
 /* Functions to initialize and free target_ops and its private data.  As well
    as functions for targets to_open/close/detach hooks.  */
@@ -736,6 +815,12 @@  lk_try_push_target ()
 
   if (!target_is_pushed (lk_target_ops))
     push_target (lk_ops->target ());
+
+  if (lk_ifdef (LK_CONFIG_MODULES))
+    lk_init_modules (gdbarch);
+  else
+    warning (_("Could not find all symbols for module support.  "
+	       "Module support turned off."));
 }
 
 /* Function for targets to_open hook.  */
@@ -842,6 +927,7 @@  init_lk_target_ops (void)
   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_xfer_partial = lk_xfer_partial;
 
   t->to_stratum = thread_stratum;
   t->to_magic = OPS_MAGIC;
diff --git a/gdb/lk-low.h b/gdb/lk-low.h
index 39c0d88f43..31ae4d7966 100644
--- a/gdb/lk-low.h
+++ b/gdb/lk-low.h
@@ -23,11 +23,14 @@ 
 #include <unordered_map>
 
 #include "gdbtypes.h"
+#include "lk-modules.h"
 #include "target.h"
 
 /* Copied constants defined in Linux kernel.  */
 #define LK_TASK_COMM_LEN 16
 #define LK_BITS_PER_BYTE 8
+#define LK_MODULE_NAME_LEN 56
+#define LK_UTS_NAME_LEN 64
 
 /* Definitions used in linux kernel target.  */
 #define LK_CPU_INVAL -1U
@@ -104,6 +107,30 @@  public:
   virtual void get_registers (CORE_ADDR task, struct target_ops *target,
 			      struct regcache *regcache, int regnum) = 0;
 
+  /* Check if address ADDR is a kernel virtual address.
+     NOTE: This hook is called in the context of target beneath.  */
+  virtual bool is_kvaddr (CORE_ADDR addr) = 0;
+
+  /* Translate virtual adress ADDR to a pysical address using page table
+     located at PGD.
+     NOTE: This hook is called in the context of target beneath.  */
+  virtual CORE_ADDR vtop (CORE_ADDR addr, CORE_ADDR pgd) = 0;
+
+  /* Translate a kernel virtual address ADDR to a physical address.
+     NOTE: This hook is called in the context of target beneath.  */
+  virtual CORE_ADDR kvtop (CORE_ADDR addr)
+  {
+    CORE_ADDR pgd = lk_read_addr (address ("init_mm")
+				  + offset ("mm_struct->pgd"));
+    return vtop (addr, pgd);
+  }
+
+  /* Add offset between a modules base and the start of its first section.
+     Equivalent to
+     <linux>/include/linux/moduleloader.h:module_frob_arch_sections  */
+  virtual void adjust_module_layout (CORE_ADDR mod, lk_module *module)
+  {}
+
   /* 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);
diff --git a/gdb/lk-modules.c b/gdb/lk-modules.c
new file mode 100644
index 0000000000..18e31aadda
--- /dev/null
+++ b/gdb/lk-modules.c
@@ -0,0 +1,501 @@ 
+/* Handle Linux kernel modules as shared libraries.
+
+   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 "common/filestuff.h"
+#include "filenames.h"
+#include "gdbcmd.h"
+#include "gdbcore.h"
+#include "gdb_regex.h"
+#include "lk-list.h"
+#include "lk-low.h"
+#include "lk-modules.h"
+#include "objfiles.h"
+#include "observer.h"
+#include "readline/readline.h"
+#include "solib.h"
+#include "solist.h"
+#include "utils.h"
+
+#include <unordered_map>
+
+static struct target_so_ops *lk_modules_so_ops = NULL;
+
+
+/* See comment in lk-modules.h.  */
+
+void
+lk_module_section_type::clear ()
+{
+  base = 0;
+  offset = 0;
+  size = 0;
+}
+
+/* Constructor for module_layout located at address LAYOUT.
+   Linux 4.5+ only! */
+
+lk_module_layout::lk_module_layout (CORE_ADDR layout)
+{
+  base = lk_read_addr (layout + lk_offset ("module_layout->base"));
+  size = lk_read_uint (layout + lk_offset ("module_layout->size"));
+
+  text.base = base;
+  text.size = lk_read_uint (layout
+			    + lk_offset ("module_layout->text_size"));
+
+  ro_data.base = base + text.size;
+  ro_data.size = lk_read_uint (layout
+			       + lk_offset ("module_layout->ro_size"));
+
+  if (lk_ops->has_field ("module_layout->ro_after_init_size")) /* linux 4.8+ */
+    {
+      ro_after_init_data.base = base + ro_data.size;
+      ro_after_init_data.size
+	= lk_read_uint (layout
+			+ lk_offset ("module_layout->ro_after_init_size"));
+
+      rw_data.base = base + ro_after_init_data.size;
+    }
+  else /* linux 4.5 - 4.8 */
+    {
+      rw_data.base = base + ro_data.size;
+    }
+
+  rw_data.size = size;
+}
+
+/* See comment in lk-modules.h.  */
+
+void
+lk_module_layout::clear ()
+{
+  base = 0;
+  size = 0;
+
+  text.clear ();
+  ro_data.clear ();
+  rw_data.clear ();
+}
+
+/* Init MODULE from struct module located at address MOD.
+   Linux -4.4 only!  */
+
+static void
+lk_module_init_module_from_struct_module (CORE_ADDR mod, lk_module *module)
+{
+  module->core = new lk_module_layout;
+  module->init = new lk_module_layout;
+
+  /* Short-hand access.  */
+  lk_module_layout *core = module->core;
+  lk_module_layout *init = module->init;
+
+  /* Core layout.  */
+  core->base = lk_read_addr (mod + lk_offset ("module->module_core"));
+  core->size = lk_read_uint (mod + lk_offset ("module->core_size"));
+
+  core->text.base = core->base;
+  core->text.size = lk_read_uint (mod
+				  + lk_offset ("module->core_text_size"));
+
+  core->ro_data.base = core->base + core->text.size;
+  core->ro_data.size = lk_read_uint (mod
+				     + lk_offset ("module->core_ro_size"));
+
+  core->rw_data.base = core->base + core->ro_data.size;
+  core->rw_data.size = core->size;
+
+  /* Init layout.  */
+  init->base = lk_read_addr (mod + lk_offset ("module->module_init"));
+  init->size = lk_read_uint (mod + lk_offset ("module->init_size"));
+
+  init->text.base = init->base;
+  init->text.size = lk_read_uint (mod
+				  + lk_offset ("module->init_text_size"));
+
+  init->ro_data.base = init->base + init->text.size;
+  init->ro_data.size = lk_read_uint (mod
+				     + lk_offset ("module->init_ro_size"));
+
+  init->rw_data.base = init->base + init->ro_data.size;
+  init->rw_data.size = init->size;
+}
+
+/* Constructor for kernel module from struct module located at MOD.  */
+
+lk_module::lk_module (CORE_ADDR mod)
+{
+  state = (lk_module_state) lk_read_int (mod + lk_offset ("module->state"));
+
+  if (lk_ops->has_field ("module->module_core")) /* linux -4.4 */
+    {
+      lk_module_init_module_from_struct_module (mod, this);
+    }
+  else /* linux 4.5+ */
+    {
+      core = new lk_module_layout (mod + lk_offset ("module->core_layout"));
+      init = new lk_module_layout (mod + lk_offset ("module->init_layout"));
+    }
+
+  if (lk_ifdef (LK_CONFIG_SMT))
+    {
+      percpu.base = lk_read_addr (mod + lk_offset ("module->percpu"));
+      percpu.size = lk_read_uint (mod + lk_offset ("module->percpu_size"));
+    }
+
+  /* Architectures can adjust the sections in the kernel (via
+     module_frob_arch_sections in <linux>/kernel/module.c:layout_and_allocate).
+     Give them a chance to do the same in GDB.  */
+  lk_ops->adjust_module_layout (mod, this);
+}
+
+lk_module::~lk_module ()
+{
+  delete core;
+  delete init;
+}
+
+/* See comment in lk-modules.h.  */
+
+void
+lk_module::clear ()
+{
+  state = LK_MODULE_STATE_INVALID;
+
+  core->clear ();
+  init->clear ();
+
+  percpu.clear ();
+}
+
+/* Check if debug info for module NAME is loaded.  */
+
+bool
+lk_modules_debug_info_loaded (const std::string &name)
+{
+  for (so_list *so = master_so_list (); so != NULL; so = so->next)
+    if (name == so->so_original_name)
+      return (so->symbols_loaded && objfile_has_symbols (so->objfile));
+
+  return false;
+}
+
+/* Replace tags, like '$release', with corresponding data in
+   solib_search_path.
+
+   Known tags:
+   $release	Linux kernel release, same as 'uname -r'
+
+   Returns the expanded path.  */
+
+static std::string
+lk_modules_expand_search_path ()
+{
+  char release[LK_UTS_NAME_LEN + 1];
+  CORE_ADDR utsname;
+
+  utsname = lk_address ("init_uts_ns") + lk_offset ("uts_namespace->name");
+  read_memory_string (utsname + lk_offset ("utsname->release"),
+		      release, LK_UTS_NAME_LEN);
+  release[LK_UTS_NAME_LEN] = '\0';
+
+  std::string search_path = get_solib_search_path ();
+  substitute_path_component (search_path, "$release", release);
+
+  return search_path;
+}
+
+/* With kernel modules there is the problem that the kernel only stores
+   the modules name but not the path from wich it was loaded from.
+   Thus we need to map the name to a path GDB can read from.  We use file
+   modules.order to do so.  It is created by kbuild containing the order in
+   which the modules appear in the Makefile and is also used by modprobe.
+   The drawback of this method is that it needs the modules.order file and
+   all relative paths, starting from <solib-search-path>, must be exactly the
+   same as decribed in it.  */
+
+/* Open file <solib-search-path>/modules.order and return its file
+   pointer.  */
+
+static gdb_file_up
+lk_modules_open_mod_order ()
+{
+  std::string filename = concat_path (lk_modules_expand_search_path (),
+				      "modules.order");
+  gdb_file_up mod_order = gdb_fopen_cloexec (filename.c_str (), "r");
+
+  if (!mod_order)
+    {
+      error (_("\
+Can not find file module.order at %s \
+to load module symbol files.\n\
+Please check if solib-search-path is set correctly."),
+	     filename.c_str ());
+    }
+
+  return mod_order;
+}
+
+/* Build map between module name and path to binary file by reading file
+   modules.order.  Returns unordered_map with module name as key and its
+   path as value.  */
+
+static std::unordered_map<std::string, std::string>
+lk_modules_build_path_map ()
+{
+  std::unordered_map<std::string, std::string> umap;
+  char line[SO_NAME_MAX_PATH_SIZE + 1];
+
+  gdb_file_up mod_order = lk_modules_open_mod_order ();
+
+  line[SO_NAME_MAX_PATH_SIZE] = '\0';
+  std::string search_path = lk_modules_expand_search_path ();
+  while (fgets (line, SO_NAME_MAX_PATH_SIZE, mod_order.get ()))
+    {
+      /* Remove trailing newline.  */
+      line[strlen (line) - 1] = '\0';
+
+      std::string name = lbasename (line);
+
+      /* 3 = strlen (".ko").  */
+      if (!endswith (name.c_str (), ".ko")
+	  || name.length () >= LK_MODULE_NAME_LEN + 3)
+	continue;
+
+      name = name.substr (0, name.length () - 3);
+
+      /* Kernel modules are named after the files they are stored in with
+	 all minus '-' replaced by underscore '_'.  Do the same to enable
+	 mapping.  */
+      for (size_t p = name.find('-'); p != std::string::npos;
+	   p = name.find ('-', p + 1))
+	name[p] = '_';
+
+      umap[name] = concat_path(search_path, line);
+    }
+
+  return umap;
+}
+
+/* Implement current_sos target_so_ops method.  */
+
+static struct so_list *
+lk_modules_current_sos (void)
+{
+  struct so_list *list = NULL;
+  std::unordered_map<std::string, std::string> umap;
+
+  umap = lk_modules_build_path_map ();
+  for (CORE_ADDR mod : lk_list ("modules", "module->list"))
+    {
+      char name[LK_MODULE_NAME_LEN];
+      CORE_ADDR name_addr;
+
+      name_addr = mod + lk_offset ("module->name");
+      read_memory_string (name_addr, name, LK_MODULE_NAME_LEN);
+
+      if (!umap.count (name))
+	continue;
+
+      struct so_list *newso = XCNEW (struct so_list);
+
+      newso->next = list;
+      list = newso;
+      newso->lm_info = new lk_module (mod);
+      strncpy (newso->so_original_name, name, SO_NAME_MAX_PATH_SIZE);
+      strncpy (newso->so_name, umap[name].c_str (), SO_NAME_MAX_PATH_SIZE);
+      newso->pspace = current_program_space;
+    }
+
+  return list;
+}
+
+/* Relocate target_section GDB_SEC using section type LK_SEC.  Helper
+   function for lk_modules_relocate_section_addresses.  */
+
+static void
+lk_modules_relocate_sec (struct target_section *gdb_sec,
+			 lk_module_section_type &lk_sec)
+{
+  unsigned int alignment = 1 << gdb_sec->the_bfd_section->alignment_power;
+  CORE_ADDR base = lk_sec.base + lk_sec.offset;
+
+  /* Adjust offset to section alignment.  */
+  if (base % alignment != 0)
+    base += alignment - (base % alignment);
+
+  gdb_sec->addr += base;
+  gdb_sec->endaddr += base;
+  lk_sec.offset += gdb_sec->endaddr - gdb_sec->addr;
+}
+
+/* Function for relocate_section_addresses hook.  */
+
+static void
+lk_modules_relocate_section_addresses (struct so_list *so,
+				       struct target_section *sec)
+{
+  lk_module *module = (lk_module *) so->lm_info;
+  unsigned int flags = sec->the_bfd_section->flags;
+  const char *name = sec->the_bfd_section->name;
+
+  /* Remove unmapped sections.  */
+  if (streq (name, ".modinfo") || streq (name, "__versions")
+      || (startswith (name, ".init")
+	  && module->state != LK_MODULE_STATE_COMING))
+    {
+      // FIXME Not mapping a section != unmapping a section
+      //
+      // In particular this causes GDB to map the "unmapped" section at the
+      // same address as the last mapped section (e.g. init.text starts at
+      // the same address like .text).  Thus printing symbols definied in the
+      // unmapped section returnes random values instead of <opimized out>.
+      return;
+    }
+
+  /* module->percpu.base points to data of cpu0.  To access the data of
+     a cpu other than cpu0 the corresponding percpu offset needs to added
+     by hand.  */
+  if (lk_ifdef (LK_CONFIG_MODULES) && endswith (name, ".percpu"))
+    {
+      lk_modules_relocate_sec (sec, module->percpu);
+      return;
+    }
+
+  lk_module_layout *layout;
+  if (startswith (name, ".init"))
+    layout = module->init;
+  else
+    layout = module->core;
+
+  if (flags & SEC_CODE)
+    lk_modules_relocate_sec (sec, layout->text);
+  else if (endswith (name, ".ro_after_init"))
+    lk_modules_relocate_sec (sec, layout->ro_after_init_data);
+  else if (flags & SEC_READONLY)
+    lk_modules_relocate_sec (sec, layout->ro_data);
+  else if (flags & SEC_ALLOC)
+    lk_modules_relocate_sec (sec, layout->rw_data);
+
+  /* Set address range displayed with 'info shared' to match core_layout.  */
+  if (so->addr_low == so->addr_high)
+    {
+      so->addr_low = module->core->base;
+      so->addr_high = module->core->base + module->core->size;
+    }
+}
+
+/* Implement free_so target_so_ops method.  */
+
+static void
+lk_modules_free_so (struct so_list *so)
+{
+  delete so->lm_info;
+}
+
+/* Implement clear_so target_so_ops method.  */
+
+static void
+lk_modules_clear_so (struct so_list *so)
+{
+  lk_module *module = (lk_module *) so->lm_info;
+  gdb_assert (module != NULL);
+
+  module->clear ();
+}
+
+/* Implement clear_solib target_so_ops method.  */
+
+static void
+lk_modules_clear_solib ()
+{
+  /* Nothing to do.  */
+}
+
+/* Implement clear_create_inferior_hook target_so_ops method.  */
+
+static void
+lk_modules_create_inferior_hook (int from_tty)
+{
+  /* Nothing to do.  */
+}
+
+/* Implement in_dynsym_resolve_code target_so_ops method.  */
+
+static int
+lk_modules_in_dynsym_resolve_code (CORE_ADDR pc)
+{
+  return 0;
+}
+
+/* Implement same target_so_ops method.  Assume the module to be different
+   if its state changed.  Otherwise there might be problems with pruning the
+   .init sections if the module was first seen during init.  */
+
+static int
+lk_modules_same (struct so_list *gdb, struct so_list *inf)
+{
+  lk_module_state gdb_state = ((lk_module *) gdb->lm_info)->state;
+  lk_module_state inf_state = ((lk_module *) inf->lm_info)->state;
+
+  return streq (gdb->so_name, inf->so_name) && gdb_state == inf_state;
+}
+
+/* See lk-modules.h.  */
+
+void
+lk_init_modules (struct gdbarch *gdbarch)
+{
+  set_solib_ops (gdbarch, lk_modules_so_ops);
+}
+
+/* Initialize linux modules solib target.  */
+
+static void
+init_lk_modules_so_ops (void)
+{
+  struct target_so_ops *t;
+
+  if (lk_modules_so_ops != NULL)
+    return;
+
+  t = XCNEW (struct target_so_ops);
+  t->relocate_section_addresses = lk_modules_relocate_section_addresses;
+  t->free_so = lk_modules_free_so;
+  t->clear_so = lk_modules_clear_so;
+  t->clear_solib = lk_modules_clear_solib;
+  t->solib_create_inferior_hook = lk_modules_create_inferior_hook;
+  t->current_sos = lk_modules_current_sos;
+  t->bfd_open = solib_bfd_open;
+  t->in_dynsym_resolve_code = lk_modules_in_dynsym_resolve_code;
+  t->same = lk_modules_same;
+
+  lk_modules_so_ops = t;
+}
+
+/* Provide a prototype to silence -Wmissing-prototypes.  */
+extern initialize_file_ftype _initialize_lk_modules;
+
+void
+_initialize_lk_modules (void)
+{
+  init_lk_modules_so_ops ();
+}
diff --git a/gdb/lk-modules.h b/gdb/lk-modules.h
new file mode 100644
index 0000000000..44d86a2a95
--- /dev/null
+++ b/gdb/lk-modules.h
@@ -0,0 +1,148 @@ 
+/* Handle kernel modules as shared libraries.
+
+   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_MODULES_H__
+#define __LK_MODULES_H__
+
+#include "solist.h"
+
+/* Copied from <linux>/include/linux/module.h.  */
+
+enum
+lk_module_state
+{
+  LK_MODULE_STATE_INVALID = -1, /* Invalid state, GDB only.  */
+  LK_MODULE_STATE_LIVE,		/* Normal state. */
+  LK_MODULE_STATE_COMING,	/* Full formed, running module_init. */
+  LK_MODULE_STATE_GOING,	/* Going away. */
+  LK_MODULE_STATE_UNFORMED,	/* Still setting it up. */
+};
+
+/* The kernel modules .ko files are only partially linked, i.e. they contain
+   section headers but no program headers.  When the module is loaded the
+   sections are relocated by the kernel (<linux>/kernel/module.c:load_module
+   & layout_and_allocate & layout_sections).  Here the kernel destinguishes
+   between different section types
+
+   * text (SHF_EXECINSTR | SHF_ALLOC)
+   * read-only data (SHF_ALLOC & !SHF_WRITE)
+   * read-only-after-init data (SHF_ALLOC | SHF_RO_AFTER_INIT)
+   * read-write data (SHF_WRITE | SHF_ALLOC)
+
+   combined in module layouts.
+
+   There are two module layouts, init and core.  The init module layout
+   contains all sections where the section name starts with ".init".  The
+   content of the init module layout gets unlinked once the module is
+   loaded.  The core module layout contains the "normal" text and data
+   needed during module runtime.
+
+   Furthermore there can be a
+
+   * data..percpu (<linux>/kernel/module.c:percpu_modalloc)
+
+   section which gets relocated to the other percpu data. */
+
+struct lk_module_section_type
+{
+  /* Lowest address of this section type.  */
+  CORE_ADDR base = 0;
+
+  /* Size of this section type.  Calculated by GDB during relocation.  */
+  CORE_ADDR offset = 0;
+
+  /* Accumulated size of all section types "below".  Read from kernel
+     (except for rw_data).  */
+  unsigned int size = 0;
+
+  /* Reset this module_section to default (all zero).  Needed for
+     target_so_ops->clear_so hook.  */
+  void clear ();
+};
+
+/* Copied from <linux>/include/linux/module.h: struct module_layout.  */
+
+struct lk_module_layout
+{
+  lk_module_layout () = default;
+  lk_module_layout (CORE_ADDR layout);
+
+  /* Reset this module_layout to default (all zero).  Needed for
+     target_so_ops->clear_so hook.  */
+  void clear ();
+
+  /* Lowest address of this module layout.  */
+  CORE_ADDR base = 0;
+
+  /* Total size.  */
+  unsigned int size = 0;
+
+  /* Executable text.
+     text.size = text.  */
+  lk_module_section_type text;
+
+  /* Read-only data.
+     ro_data.size = text + ro_data.  */
+  lk_module_section_type ro_data;
+
+  /* Read-only-after-init data.
+     ro_after_init_data.size = text + ro_data + ro_after_init_data.  */
+  lk_module_section_type ro_after_init_data; /* linux 4.8+ */
+
+  /* Writable data.
+     rw_data.size = layout.size.  */
+  lk_module_section_type rw_data;
+};
+
+/* Link map info to include in an allocated so_list entry.  */
+
+struct lk_module : public lm_info_base
+{
+  lk_module () = delete;
+  lk_module (CORE_ADDR mod);
+
+  ~lk_module ();
+
+  /* Reset this module to default (all zero).  Needed for
+     target_so_ops->clear_so hook.  */
+  void clear ();
+
+  /* This modules state as seen from the kernel.  */
+  lk_module_state state = LK_MODULE_STATE_INVALID;
+
+  /* "core" layout, i.e. text + data after init.  */
+  lk_module_layout *core;
+
+  /* "init" layout, i.e. text + data only needed during init
+     (LK_MODULE_STATE_COMING).  It is unmapped after initialization.  */
+  lk_module_layout *init;
+
+  /* Per CPU data.  Mapped together with "ordinary" per cpu data.  */
+  lk_module_section_type percpu;
+};
+
+
+/* Check if debug info for module NAME are loaded.  Needed by lsmod command.  */
+
+extern bool lk_modules_debug_info_loaded (const std::string &name);
+
+/* Initialize module support for lk target.  */
+extern void lk_init_modules (struct gdbarch *gdbarch);
+
+#endif /*  __LK_MODULES_H__  */
diff --git a/gdb/solib.c b/gdb/solib.c
index 1c78845938..c411cc6124 100644
--- a/gdb/solib.c
+++ b/gdb/solib.c
@@ -105,6 +105,14 @@  show_solib_search_path (struct ui_file *file, int from_tty,
 		    value);
 }
 
+/* see solib.h.  */
+
+const char *
+get_solib_search_path ()
+{
+  return solib_search_path ? solib_search_path : "";
+}
+
 /* Same as HAVE_DOS_BASED_FILE_SYSTEM, but useable as an rvalue.  */
 #if (HAVE_DOS_BASED_FILE_SYSTEM)
 #  define DOS_BASED_FILE_SYSTEM 1
diff --git a/gdb/solib.h b/gdb/solib.h
index f6f02ad77a..8d58cc0675 100644
--- a/gdb/solib.h
+++ b/gdb/solib.h
@@ -31,6 +31,11 @@  struct program_space;
 /* List of known shared objects */
 #define so_list_head current_program_space->so_list
 
+/* Returns the solib_search_path.  The returned string is malloc'ed and must be
+   freed by the caller.  */
+
+extern const char *get_solib_search_path ();
+
 /* Called when we free all symtabs, to free the shared library information
    as well.  */