[RFC v4 9/9] Add S390 support for linux-kernel target

Philipp Rudo
2017-06-12T21:17:09Z

After implementing the new linux-kernel target and preparing s390-tdep.
It is now time to get everything to work. Thus implement the hooks
required by the linux-kernel target and enable s390's privileged
registers.

gdb/ChangeLog:

* s390-lk-tdep.h: New file.
* s390-lk-tdep.c: New file.
* Makefile.in (ALL_TARGET_OBS): Add s390-lk-tdep.o.
(HFILES_NO_SRCDIR): Add s390-lk-tdep.h.
(ALLDEPFILES): Add s390-lk-tdep.c.
* configure.tgt (s390*-*-linux*): Add s390-lk-tdep.o.
* s390-tdep.h: Define macros for address translation.
* s390-tdep.c (s390-lk-tdep.h): New include.
(s390_iterate_over_regset_sections): Enable privileged registers.
(s390_core_read_description): Enable privileged registers.
(s390_gdbarch_init): Enable privileged registers and adjust.
---
gdb/Makefile.in | 3 +
gdb/configure.tgt | 5 +-
gdb/s390-lk-tdep.c | 887 +++++++++++++++++++++++++++++++++++++++++++++++++++++
gdb/s390-lk-tdep.h | 54 ++++
gdb/s390-tdep.c | 57 +++-
5 files changed, 1001 insertions(+), 5 deletions(-)
create mode 100644 gdb/s390-lk-tdep.c
create mode 100644 gdb/s390-lk-tdep.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 9819b70f3d..cb31b65b5e 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -873,6 +873,7 @@ ALL_TARGET_OBS = \
rs6000-tdep.o \
rx-tdep.o \
s390-linux-tdep.o \
+ s390-lk-tdep.o \
s390-tdep.o \
score-tdep.o \
sh-linux-tdep.o \
@@ -1448,6 +1449,7 @@ HFILES_NO_SRCDIR = \
rs6000-aix-tdep.h \
rs6000-tdep.h \
s390-linux-tdep.h \
+ s390-lk-tdep.h \
s390-tdep.h \
score-tdep.h \
selftest-arch.h \
@@ -2655,6 +2657,7 @@ ALLDEPFILES = \
rx-tdep.c \
s390-linux-nat.c \
s390-linux-tdep.c \
+ s390-lk-tdep.c \
s390-tdep.c \
score-tdep.c \
ser-go32.c \
diff --git a/gdb/configure.tgt b/gdb/configure.tgt
index b79a0bac4c..2788368c17 100644
--- a/gdb/configure.tgt
+++ b/gdb/configure.tgt
@@ -483,8 +483,9 @@ powerpc*-*-*)

s390*-*-linux*)
# Target: S390 running Linux
- gdb_target_obs="s390-tdep.o s390-linux-tdep.o solib-svr4.o \
- linux-tdep.o linux-record.o ${lk_target_obs}"
+ gdb_target_obs="s390-tdep.o s390-linux-tdep.o s390-lk-tdep.o \
+ solib-svr4.o linux-tdep.o linux-record.o \
+ ${lk_target_obs}"
build_gdbserver=yes
;;

diff --git a/gdb/s390-lk-tdep.c b/gdb/s390-lk-tdep.c
new file mode 100644
index 0000000000..06da7795f0
--- /dev/null
+++ b/gdb/s390-lk-tdep.c
@@ -0,0 +1,887 @@
+/* Target-dependent code for linux-kernel target on S390.
+ 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/>. */
+
+#include "defs.h"
+
+#include "dwarf2-frame.h"
+#include "frame-base.h"
+#include "frame-unwind.h"
+#include "gdbarch.h"
+#include "gdbcore.h"
+#include "gdbthread.h"
+#include "lk-low.h"
+#include "lk-modules.h"
+#include "osabi.h"
+#include "regcache.h"
+#include "regset.h"
+#include "s390-tdep.h"
+#include "s390-lk-tdep.h"
+
+#include "features/s390x-cr-linux64.c"
+#include "features/s390x-vxcr-linux64.c"
+
+/* Register maps and sets. */
+
+static const struct regcache_map_entry s390x_gregmap_lk[] =
+ {
+ { 10, S390_R6_REGNUM }, /* r0-r5 volatile */
+ { -2, REGCACHE_MAP_SKIP, 8 },
+ { 1, S390_PSWA_REGNUM }, /* Use r14 for PSWA. */
+ { 0 }
+ };
+
+static const struct regcache_map_entry s390x_regmap_cr [] =
+ {
+ { 16, S390_CR0_REGNUM, 8 },
+ { 0 }
+ };
+
+static const struct regcache_map_entry s390x_regmap_timer [] =
+ {
+ { 1, S390_TIMER_REGNUM, 8 },
+ { 0 }
+ };
+
+static const struct regcache_map_entry s390x_regmap_todcmp [] =
+ {
+ { 1, S390_TODCMP_REGNUM, 8 },
+ { 0 }
+ };
+
+static const struct regcache_map_entry s390x_regmap_todpreg [] =
+ {
+ { 1, S390_TODPREG_REGNUM, 4 },
+ { 0 }
+ };
+
+static const struct regcache_map_entry s390x_regmap_prefix [] =
+ {
+ { 1, S390_PREFIX_REGNUM, 4 },
+ { 0 }
+ };
+
+const struct regset s390x_gregset_lk = {
+ s390x_gregmap_lk,
+ regcache_supply_regset,
+ regcache_collect_regset
+};
+
+const struct regset s390x_cr_regset = {
+ s390x_regmap_cr,
+ regcache_supply_regset,
+ regcache_collect_regset
+};
+
+const struct regset s390x_timer_regset = {
+ s390x_regmap_timer,
+ regcache_supply_regset,
+ regcache_collect_regset
+};
+
+const struct regset s390x_todcmp_regset = {
+ s390x_regmap_todcmp,
+ regcache_supply_regset,
+ regcache_collect_regset
+};
+
+const struct regset s390x_todpreg_regset = {
+ s390x_regmap_todpreg,
+ regcache_supply_regset,
+ regcache_collect_regset
+};
+
+const struct regset s390x_prefix_regset = {
+ s390x_regmap_prefix,
+ regcache_supply_regset,
+ regcache_collect_regset
+};
+
+
+/* General Helper functions. */
+
+/* Get the lowcore of CPU. */
+
+CORE_ADDR
+s390_lk_get_lowcore (unsigned int cpu)
+{
+ CORE_ADDR lowcore_ptr, lowcore;
+ size_t ptr_len = lk_builtin_type_size (unsigned_long);
+
+ lowcore_ptr = LK_ADDR (lowcore_ptr) + (ptr_len * cpu);
+ return lk_read_addr (lowcore_ptr);
+}
+
+
+/* Linux Kernel unwinder. */
+
+/* Helper functions and definitions. */
+
+struct s390_lk_unwind_cache
+{
+ /* The frame this frame_cache belongs to and its ... */
+ struct frame_info *frame;
+
+ /* ... frame base, ... */
+ CORE_ADDR frame_base;
+ /* ... stack pointer, ... */
+ CORE_ADDR sp;
+ /* ... process counter, ... */
+ CORE_ADDR pc;
+ /* ... PSW mask, ... */
+ CORE_ADDR pswm;
+ /* ... PSW address and ... */
+ CORE_ADDR pswa;
+ /* ... saved general purpose registers. */
+ CORE_ADDR gprs[S390_NUM_GPRS];
+
+ /* Mask to store which gprs where saved. */
+ unsigned long gprs_p:S390_NUM_GPRS;
+ /* Does this frame contain a zero backchain? */
+ bool zero_backchain;
+};
+
+/* S390s kernel stack is split up on several memory locations:
+
+ - kernel stack (per task)
+ - async aka. interrupt stack (per cpu)
+ - panic stack (per cpu)
+ - restart stack (unique, global)
+
+ Each memory location is page aligned and has a size of four consecutive
+ pages (except the panic stack with only one page). */
+
+enum s390_lk_stack_location
+{
+ S390_LK_STACK_INVALID = -1,
+ S390_LK_STACK_USER,
+ S390_LK_STACK_KERNEL,
+ S390_LK_STACK_ASYNC,
+ S390_LK_STACK_PANIC,
+ S390_LK_STACK_RESTART
+};
+
+/* Get value of gpr REGNUM stored in CACHE. */
+
+static inline struct value *
+s390_lk_unwind_get_gpr (struct s390_lk_unwind_cache *cache, int regnum)
+{
+ gdb_assert (regnum >= S390_R0_REGNUM && regnum <= S390_R15_REGNUM);
+
+ unsigned int gpr = regnum - S390_R0_REGNUM;
+ if (!(cache->gprs_p & (1 << gpr)))
+ return frame_unwind_got_optimized (cache->frame, regnum);
+
+ /* When we hit a zero_backchain R15 is the (computed) lowest address of
+ pt_regs. */
+ if (regnum == S390_R15_REGNUM && cache->zero_backchain)
+ return frame_unwind_got_address (cache->frame, regnum, cache->gprs[gpr]);
+
+ return frame_unwind_got_memory (cache->frame, regnum, cache->gprs[gpr]);
+}
+
+/* Store ADDR of gpr REGNUM in CACHE. */
+
+static inline void
+s390_lk_unwind_set_gpr (struct s390_lk_unwind_cache *cache, int regnum,
+ CORE_ADDR addr)
+{
+ gdb_assert (regnum >= S390_R0_REGNUM && regnum <= S390_R15_REGNUM);
+
+ unsigned int gpr = regnum - S390_R0_REGNUM;
+ cache->gprs[gpr] = addr;
+ cache->gprs_p |= (1 << gpr);
+}
+
+/* Allocate and initialize a new s390_lk_unwind_cache. */
+
+static struct s390_lk_unwind_cache *
+s390_lk_alloc_unwind_cache ()
+{
+ struct s390_lk_unwind_cache *cache;
+
+ cache = FRAME_OBSTACK_ZALLOC (struct s390_lk_unwind_cache);
+
+ cache->frame = NULL;
+ cache->frame_base = -1;
+ cache->zero_backchain = false;
+
+ cache->sp = -1;
+ cache->pc = -1;
+ cache->pswm = -1;
+ cache->pswa = -1;
+ for (CORE_ADDR *gpr = cache->gprs; gpr < cache->gprs + S390_NUM_GPRS; gpr++)
+ *gpr = -1;
+ cache->gprs_p = 0;
+
+ return cache;
+}
+
+/* Helper macro for s390_lk_get_stack_location to check for stacks which
+ locations are stored in the lowcore.
+
+ _addr address to be checked for
+ _lc address of the corresponding lowcore
+ _stack field name of stack in lowcore
+ _type type to be returned if _addr is on _stack
+ _size size of _stack
+*/
+
+#define s390_lk_check_lowcore_stack(_addr, _lc, _stack, _type, _size) \
+ do \
+ { \
+ CORE_ADDR _##_stack, _##_stack##_top, _##_stack##_bottom; \
+ _##_stack = lk_read_addr ((_lc) + LK_OFFSET (lowcore, _stack)); \
+ _##_stack##_top = S390_LK_ROUNDUP (_##_stack, S390_LK_PAGESIZE); \
+ _##_stack##_bottom = _##_stack##_top - (_size); \
+ if ((_addr) <= _##_stack##_top && (_addr) >= _##_stack##_bottom) \
+ return (_type); \
+ } \
+ while (0)
+
+/* Find and return the stack location of ADDR belonging to TASK. */
+
+static enum s390_lk_stack_location
+s390_lk_get_stack_location (CORE_ADDR task, CORE_ADDR addr)
+{
+ CORE_ADDR lowcore, top, bottom;
+ unsigned int cpu = lk_task_running (task);
+
+
+ /* Kernel stack. */
+ bottom = lk_read_addr (task + LK_OFFSET (task_struct, stack));
+ top = bottom + S390_LK_STACKSIZE;
+ if (addr <= top && addr >= bottom)
+ return S390_LK_STACK_KERNEL;
+
+ /* A sleeping task only has the kernel stack. If a sleeping task reaches
+ this point ADDR isn't on the stack. */
+ if (cpu == LK_CPU_INVAL)
+ return S390_LK_STACK_INVALID;
+
+ lowcore = s390_lk_get_lowcore (cpu);
+
+ /* Async aka. interrupt stack. */
+ s390_lk_check_lowcore_stack (addr, lowcore, async_stack,
+ S390_LK_STACK_ASYNC, S390_LK_ASYNCSIZE);
+
+ /* Panic stack.
+ Note: The panic stack only has a size of one page. */
+ s390_lk_check_lowcore_stack (addr, lowcore, panic_stack,
+ S390_LK_STACK_PANIC, S390_LK_PAGESIZE);
+
+ /* Restart stack. */
+ s390_lk_check_lowcore_stack (addr, lowcore, restart_stack,
+ S390_LK_STACK_RESTART, S390_LK_ASYNCSIZE);
+
+ return S390_LK_STACK_INVALID;
+}
+
+
+/* Unwind a single NORMAL_FRAME, i.e. a struct stack_frame. */
+
+static struct s390_lk_unwind_cache *
+s390_lk_frame_unwind_cache (struct frame_info *this_frame,
+ void **this_cache)
+{
+ struct s390_lk_unwind_cache *cache;
+ CORE_ADDR backchain, backchain_addr, prev_gpr15;
+
+ if (*this_cache)
+ return (struct s390_lk_unwind_cache *) *this_cache;
+
+ cache = s390_lk_alloc_unwind_cache ();
+ *this_cache = cache;
+
+ cache->frame = this_frame;
+ /* For NORMAL_FRAMES, cache->sp == lowest address of struct stack_frame. */
+ cache->sp = get_frame_sp (this_frame);
+ cache->pc = get_frame_pc (this_frame);
+ /* Choose the frame's base to be gpr15 + sizeof (stack_frame) to be
+ complient with DWARF's DW_CFA_def_cfa. Otherwise gdbarch_inner_than
+ can trigger false stack corruption asserts, as
+
+ next_dwarf_frame_base = next_backchain_frame_base + sizeof (stack_frame)
+ > this_backchain_frame_base
+ */
+ cache->frame_base = cache->sp + TYPE_LENGTH (LK_STRUCT (stack_frame));
+
+ backchain_addr = cache->sp + LK_OFFSET (stack_frame, back_chain);
+ backchain = lk_read_addr (backchain_addr);
+
+ /* A zero backchain marks the end of this stack. This can lead to two
+ actions (handled by SIGTRAMP_FRAME unwinder):
+ * stop unwinding (if sp at the end of the kernel stack)
+ * jump to next stack location (otherwise).
+ */
+ if (backchain == 0)
+ {
+ CORE_ADDR pt_regs;
+
+ pt_regs = cache->sp + TYPE_LENGTH (LK_STRUCT (stack_frame));
+
+ {
+ /* A bug in the kernel (linux 3.10+) adds the stack overhead twice
+ for swapper tasks (!= swapper/0) with the information written in
+ the first (higher address) pt_regs. Compensate this by updating
+ the stack pointer. */
+ enum s390_lk_stack_location stack;
+
+ CORE_ADDR task = frame_get_thread (this_frame)->ptid.tid ();
+ long pid = frame_get_thread (this_frame)->ptid.lwp ();
+ stack = s390_lk_get_stack_location (task, cache->sp);
+ if (pid == 0 && task != LK_ADDR (init_task)
+ && stack == S390_LK_STACK_KERNEL)
+ pt_regs += (TYPE_LENGTH (LK_STRUCT (stack_frame))
+ + TYPE_LENGTH (LK_STRUCT (pt_regs)));
+ }
+
+ cache->zero_backchain = true;
+ s390_lk_unwind_set_gpr (cache, S390_R15_REGNUM, pt_regs);
+
+ /* Use pc = 0 to mark end of stack. */
+ s390_lk_unwind_set_gpr (cache, S390_R14_REGNUM, backchain_addr);
+ return cache;
+ }
+
+ /* Do sanity check. */
+ prev_gpr15 = lk_read_addr (backchain
+ + s390_lk_stack_frame_gpr (S390_R15_REGNUM));
+ if (backchain != prev_gpr15)
+ {
+ size_t ptr_len = lk_builtin_type_size (unsigned_long);
+ CORE_ADDR task = frame_get_thread (this_frame)->ptid.tid ();
+ error (_("Stack corruption for task 0x%s at stack frame 0x%s with "
+ "back_chain 0x%s detected %s. back_chain->gpr15 != back_chain."),
+ phex (task, ptr_len), phex (cache->sp, ptr_len),
+ phex (backchain, ptr_len), phex (prev_gpr15, ptr_len));
+ }
+
+ s390_lk_unwind_set_gpr (cache, S390_R15_REGNUM, backchain_addr);
+ s390_lk_unwind_set_gpr (cache, S390_R14_REGNUM,
+ backchain + s390_lk_stack_frame_gpr (S390_R14_REGNUM));
+
+ return cache;
+}
+
+/* Function for frame_unwind->this_id hook for NORMAL_FRAMEs. */
+
+static void
+s390_lk_frame_this_id (struct frame_info *this_frame,
+ void **this_cache,
+ struct frame_id *this_id)
+{
+ struct s390_lk_unwind_cache *cache
+ = s390_lk_frame_unwind_cache (this_frame, this_cache);
+
+ /* If we don't have a stack frame... */
+ if (cache->sp == -1)
+ {
+ /* ...but a PC. We have a inline frame. */
+ if (cache->pc != -1)
+ *this_id = frame_id_build_unavailable_stack (cache->pc);
+
+ return;
+ }
+
+ *this_id = frame_id_build (cache->frame_base, cache->pc);
+}
+
+/* Function for frame_unwind->prev_register hook for NORMAL_ and
+ SIGTRAMP_FRAMEs. */
+
+static struct value *
+s390_lk_frame_prev_register (struct frame_info *this_frame,
+ void **this_cache, int regnum)
+{
+ struct s390_lk_unwind_cache *cache
+ = s390_lk_frame_unwind_cache (this_frame, this_cache);
+
+ /* Also handles S390_SP_REGNUM and S390_RETADDR_REGNUM as they are mapped to
+ R15 and R14 respectfully in s390-tdep.h. */
+ if (regnum >= S390_R0_REGNUM && regnum <= S390_R15_REGNUM)
+ return s390_lk_unwind_get_gpr (cache, regnum);
+
+ /* Use PSWM stored in pt_regs if available. */
+ if (regnum == S390_PSWM_REGNUM)
+ return (cache->pswm != -1
+ ? frame_unwind_got_memory (cache->frame, regnum, cache->pswm)
+ : frame_unwind_got_optimized (cache->frame, regnum));
+
+ /* Use PSWA stored in pt_regs if available, R14/PC otherwise. */
+ if (regnum == S390_PSWA_REGNUM)
+ return (cache->pswa != -1
+ ? frame_unwind_got_memory (cache->frame, regnum, cache->pswa)
+ : s390_lk_unwind_get_gpr (cache, S390_RETADDR_REGNUM));
+
+ if (regnum >= S390_NUM_REGS)
+ return s390_unwind_pseudo_register (cache->frame, regnum);
+
+ /* Default is not saved. */
+ return frame_unwind_got_optimized (cache->frame, regnum);
+}
+
+/* Simple backchain unwinder for Linux kernel on S390. Its main purpose is
+ to handle stack frames created in Assembler code without debug
+ infromation. */
+
+static const struct frame_unwind s390_lk_frame_unwind = {
+ NORMAL_FRAME,
+ default_frame_unwind_stop_reason,
+ s390_lk_frame_this_id,
+ s390_lk_frame_prev_register,
+ NULL,
+ default_frame_sniffer
+};
+
+
+/* Unwind a single SIGTRAMP_FRAME, i.e. a struct pt_regs. */
+
+static struct s390_lk_unwind_cache *
+s390_lk_sigtramp_unwind_cache (struct frame_info *this_frame,
+ void **this_cache)
+{
+ struct frame_info *next_frame;
+ struct s390_lk_unwind_cache *cache;
+ CORE_ADDR psw;
+
+ if (*this_cache)
+ return (struct s390_lk_unwind_cache *) *this_cache;
+
+ cache = s390_lk_alloc_unwind_cache ();
+ *this_cache = cache;
+
+ cache->frame = this_frame;
+ /* For SIGTRAMP_FRAMES, cache->sp = lowest address of struct pt_regs. */
+ cache->sp = get_frame_sp (this_frame);
+ cache->pc = cache->sp + s390_lk_pt_regs_gpr (S390_R14_REGNUM);
+ /* Choose the frame base to be SP + sizeof (pt_regs) aka. the end of this
+ stack. */
+ cache->frame_base = cache->sp + TYPE_LENGTH (LK_STRUCT (pt_regs));
+
+ psw = cache->sp + LK_OFFSET (pt_regs, psw);
+ cache->pswm = psw + LK_OFFSET (psw_t, mask);
+ cache->pswa = psw + LK_OFFSET (psw_t, addr);
+
+ for (int i = S390_R0_REGNUM; i <= S390_R15_REGNUM; i++)
+ s390_lk_unwind_set_gpr (cache, i, cache->sp + s390_lk_pt_regs_gpr (i));
+
+ return cache;
+}
+
+/* Function for frame_unwind->this_id hook for SIGTRAMP_FRAMEs. */
+
+static void
+s390_lk_sigtramp_this_id (struct frame_info *this_frame,
+ void **this_cache,
+ struct frame_id *this_id)
+{
+ struct s390_lk_unwind_cache *cache
+ = s390_lk_sigtramp_unwind_cache (this_frame, this_cache);
+
+ CORE_ADDR task = frame_get_thread (this_frame)->ptid.tid ();
+
+ switch (s390_lk_get_stack_location (task, cache->sp))
+ {
+ /* If we are on an interrupt stack keep on unwinding. */
+ case S390_LK_STACK_ASYNC:
+ case S390_LK_STACK_PANIC:
+ case S390_LK_STACK_RESTART:
+ *this_id = frame_id_build (cache->frame_base, cache->pc);
+ return;
+
+ /* If we are on the kernel stack check whether we are at the end of
+ the stack. If so stop unwinding otherwise continue. */
+ case S390_LK_STACK_KERNEL:
+ {
+ CORE_ADDR top;
+
+ top = lk_read_addr (task + LK_OFFSET (task_struct, stack));
+ top += S390_LK_STACKSIZE;
+
+ /* swapper/0 doesn't have a pt_regs at the top of its stack, so in
+ this case cache->sp pointing at the top of the last stack_frame
+ points at the end of the stack. */
+ if (task == LK_ADDR (init_task) && top <= cache->sp)
+ *this_id = outer_frame_id;
+
+ /* For all other tasks cache->frame_base pointing at the top of
+ pt_regs points at the end of stack. */
+ else if (top <= cache->frame_base)
+ *this_id = outer_frame_id;
+
+ /* The program check handler appends his stack to the kernel stack.
+ If that is the case simply keep on unwinding. */
+ else
+ *this_id = frame_id_build (cache->frame_base, cache->pc);
+
+ return;
+ }
+
+ /* Otherwise somewhere something went wrong. */
+ default:
+ {
+ size_t ptr_len = lk_builtin_type_size (unsigned_long);
+ error (_("Couldn't find find valid stack for task 0x%s with stack "
+ "pointer 0x%s. Stack corruption?"),
+ phex (task, ptr_len), phex (cache->sp, ptr_len));
+ }
+ }
+}
+
+/* Function for frame_unwind->sniffer hook for SIGTRAMP_FRAMEs. */
+
+static int
+s390_lk_sigtramp_sniffer (const struct frame_unwind *self,
+ struct frame_info *this_frame,
+ void **this_cache)
+{
+ struct gdbarch *gdbarch = get_frame_arch (this_frame);
+
+ if (!gdbarch_lk_init_private_p (gdbarch))
+ return 0;
+
+ CORE_ADDR pc = get_frame_pc (this_frame);
+
+ return pc == 0;
+}
+
+/* Sigtramp unwinder for Linux kernel on S390. It handles situations when
+ the end of a stack, i.e. a zero backchain, was detected. */
+
+static const struct frame_unwind s390_lk_sigtramp_unwind = {
+ SIGTRAMP_FRAME,
+ default_frame_unwind_stop_reason,
+ s390_lk_sigtramp_this_id,
+ s390_lk_frame_prev_register,
+ NULL,
+ s390_lk_sigtramp_sniffer
+};
+
+
+/* Frame base handling. */
+
+static CORE_ADDR
+s390_lk_frame_base_address (struct frame_info *this_frame, void **this_cache)
+{
+ struct s390_lk_unwind_cache *cache
+ = s390_lk_frame_unwind_cache (this_frame, this_cache);
+ return cache->frame_base;
+}
+
+static const struct frame_base s390_lk_frame_base = {
+ &s390_lk_frame_unwind,
+ s390_lk_frame_base_address,
+ s390_lk_frame_base_address,
+ s390_lk_frame_base_address
+};
+
+
+/* Hooks for linux-kernel target. */
+
+/* Function for Linux kernel target get_registers hook. Supplies gprs for
+ task TASK to REGCACHE. Uses r14 (back jump address) as current pswa. */
+
+void
+s390_lk_get_registers (CORE_ADDR task, struct target_ops *target,
+ struct regcache *regcache, int regnum)
+{
+ const struct regset *regset;
+ CORE_ADDR ksp, gprs, pswa;
+ gdb_byte buf[80]; /* 80 = 10 (#registers saved) * 8 (64 bit width) */
+ size_t size;
+
+ regset = &s390x_gregset_lk;
+
+ ksp = lk_read_addr (task + LK_OFFSET (task_struct, thread)
+ + LK_OFFSET (thread_struct, ksp));
+ gprs = ksp + LK_OFFSET (stack_frame, gprs);
+ size = FIELD_SIZE (LK_FIELD (stack_frame, gprs));
+ gdb_assert (size <= sizeof (buf));
+
+ read_memory (gprs, buf, size);
+ regset->supply_regset (regset, regcache, -1, buf, size);
+}
+
+/* Function for Linux kernel target get_percpu_offset hook. Returns the
+ percpu_offset from lowcore for cpu CPU. */
+
+CORE_ADDR
+s390_lk_get_percpu_offset (unsigned int cpu)
+{
+ CORE_ADDR lowcore = s390_lk_get_lowcore (cpu);
+ return lk_read_addr (lowcore + LK_OFFSET (lowcore, percpu_offset));
+}
+
+/* Function for Linux kernel target map_running_task_to_cpu hook. */
+
+unsigned int
+s390_lk_map_running_task_to_cpu (struct thread_info *ti)
+{
+ struct regcache *regcache;
+ enum register_status reg_status;
+ CORE_ADDR lowcore;
+ unsigned int cpu;
+
+ regcache = get_thread_regcache (ti->ptid);
+ reg_status = regcache_raw_read_unsigned (regcache, S390_PREFIX_REGNUM,
+ (ULONGEST *) &lowcore);
+ if (reg_status != REG_VALID)
+ error (_("Could not find prefix register for thread with pid %d, lwp %li."),
+ ti->ptid.pid (), ti->ptid.lwp ());
+
+ cpu = lk_read_uint (lowcore + LK_OFFSET (lowcore, cpu_nr));
+
+ return cpu;
+}
+
+/* Function for Linux kernel target is_kvaddr hook. */
+
+int
+s390_lk_is_kvaddr (CORE_ADDR addr)
+{
+ return addr >= LK_ADDR (high_memory);
+}
+
+/* Read table entry from TABLE at offset OFFSET. Helper for s390_lk_vtop. */
+
+static inline ULONGEST
+s390_lk_read_table_entry (CORE_ADDR table, ULONGEST offset)
+{
+ return lk_read_ulong (table + offset * lk_builtin_type_size (unsigned_long));
+}
+
+/* Function for Linux kernel target vtop hook. Assume 64 bit addresses. */
+
+CORE_ADDR
+s390_lk_vtop (CORE_ADDR table, CORE_ADDR vaddr)
+{
+ ULONGEST entry, offset;
+ CORE_ADDR paddr;
+ unsigned int table_type;
+ size_t addr_size = lk_builtin_type_size (unsigned_long);
+
+ /* Read first entry in table to get its type. As the Table-Type bits are
+ the same in every table assume Region1-Table. */
+ entry = s390_lk_read_table_entry (table, 0);
+ table_type = (entry & S390_LK_RFTE_TT) >> 2;
+
+ switch (table_type)
+ {
+ case S390_LK_DAT_TT_REGION1:
+ {
+ offset = (vaddr & S390_LK_VADDR_RFX) >> 53;
+ entry = s390_lk_read_table_entry (table, offset);
+
+ /* Do sanity checks. */
+ if (!entry)
+ warning (_("Trying to translate address 0x%s with empty "\
+ "region-first-table entry."),
+ phex (vaddr, addr_size));
+ else if ((entry & S390_LK_RFTE_TT) >> 2 != S390_LK_DAT_TT_REGION1)
+ warning (_("Trying to translate address 0x%s with corrupt "\
+ "table type in region-first-table entry."),
+ phex (vaddr, addr_size));
+ else if (entry & S390_LK_RFTE_I)
+ warning (_("Translating address 0x%s with invalid bit set at "\
+ "region-first-table entry."),
+ phex (vaddr, addr_size));
+
+ table = entry & S390_LK_RFTE_O;
+ }
+ /* fall through */
+ case S390_LK_DAT_TT_REGION2:
+ {
+ offset = (vaddr & S390_LK_VADDR_RSX) >> 42;
+ entry = s390_lk_read_table_entry (table, offset);
+
+ /* Do sanity checks. */
+ if (!entry)
+ warning (_("Trying to translate address 0x%s with empty "\
+ "region-second-table entry."),
+ phex (vaddr, addr_size));
+ else if ((entry & S390_LK_RSTE_TT) >> 2 != S390_LK_DAT_TT_REGION2)
+ warning (_("Trying to translate address 0x%s with corrupt "\
+ "table type in region-second-table entry."),
+ phex (vaddr, addr_size));
+ else if (entry & S390_LK_RSTE_I)
+ warning (_("Translating address 0x%s with invalid bit set at "\
+ "region-second-table entry."),
+ phex (vaddr, addr_size));
+
+ table = entry & S390_LK_RSTE_O;
+ }
+ /* fall through */
+ case S390_LK_DAT_TT_REGION3:
+ {
+ offset = (vaddr & S390_LK_VADDR_RTX) >> 31;
+ entry = s390_lk_read_table_entry (table, offset);
+
+ /* Do sanity checks. */
+ if (!entry)
+ warning (_("Trying to translate address 0x%s with empty "\
+ "region-third-table entry."),
+ phex (vaddr, addr_size));
+ else if ((entry & S390_LK_RTTE_TT) >> 2 != S390_LK_DAT_TT_REGION3)
+ warning (_("Trying to translate address 0x%s with corrupt "\
+ "table type in region-third-table entry."),
+ phex (vaddr, addr_size));
+ else if (entry & S390_LK_RTTE_I)
+ warning (_("Translating address 0x%s with invalid bit set at "\
+ "region-third-table entry."),
+ phex (vaddr, addr_size));
+
+ /* Check for huge page. */
+ if (entry & S390_LK_RTTE_FC)
+ {
+ paddr = ((entry & S390_LK_RTTE_RFAA)
+ + (vaddr & ~S390_LK_RTTE_RFAA));
+ return paddr;
+ }
+
+ table = entry & S390_LK_RTTE_O;
+ }
+ /* fall through */
+ case S390_LK_DAT_TT_SEGMENT:
+ {
+ offset = (vaddr & S390_LK_VADDR_SX) >> 20;
+ entry = s390_lk_read_table_entry (table, offset);
+
+ /* Do sanity checks. */
+ if (!entry)
+ warning (_("Trying to translate address 0x%s with empty "\
+ "segment-table entry."),
+ phex (vaddr, addr_size));
+ else if ((entry & S390_LK_STE_TT) >> 2 != S390_LK_DAT_TT_SEGMENT)
+ warning (_("Trying to translate address 0x%s with corrupt "\
+ "table type in segment-table entry."),
+ phex (vaddr, addr_size));
+ else if (entry & S390_LK_STE_I)
+ warning (_("Translating address 0x%s with invalid bit set at "\
+ "segment-table entry."),
+ phex (vaddr, addr_size));
+
+ /* Check for large page. */
+ if (entry & S390_LK_STE_FC)
+ {
+ paddr = ((entry & S390_LK_STE_SFAA)
+ + (vaddr & ~S390_LK_STE_SFAA));
+ return paddr;
+ }
+
+ table = entry & S390_LK_STE_O;
+ break;
+ }
+ } /* switch (table_type) */
+
+ offset = (vaddr & S390_LK_VADDR_PX) >> 12;
+ entry = s390_lk_read_table_entry (table, offset);
+
+ /* Do sanity checks. */
+ if (!entry)
+ warning (_("Trying to translate address 0x%s with empty page-table "\
+ "entry."),
+ phex (vaddr, addr_size));
+ else if (entry & S390_LK_PTE_I)
+ warning (_("Translating address 0x%s with invalid bit set at page-table "\
+ "entry."),
+ phex (vaddr, addr_size));
+
+ paddr = ((entry & S390_LK_PTE_PFAA) + (vaddr & ~S390_LK_PTE_PFAA));
+
+ return paddr;
+}
+
+/* Function for Linux kernel target adjust_module_layout hook. */
+
+void
+s390_lk_adjust_module_layout (CORE_ADDR mod, lk_module *module)
+{
+ CORE_ADDR mod_arch;
+
+ mod_arch = mod + LK_OFFSET (module, arch);
+
+ module->core->text.base
+ += lk_read_ulong (mod_arch + LK_OFFSET (mod_arch_specific, plt_offset));
+ module->core->text.base
+ += lk_read_ulong (mod_arch + LK_OFFSET (mod_arch_specific, plt_size));
+}
+
+/* Initialize s390 dependent private data for linux kernel target. */
+
+static void
+s390_lk_init_private (struct gdbarch *gdbarch)
+{
+ LK_DECLARE_FIELD (stack_frame, gprs);
+ LK_DECLARE_FIELD (stack_frame, back_chain);
+
+ LK_DECLARE_FIELD (pt_regs, psw);
+ LK_DECLARE_FIELD (pt_regs, gprs);
+
+ LK_DECLARE_FIELD (task_struct, stack);
+
+ LK_DECLARE_FIELD (thread_struct, ksp);
+
+ LK_DECLARE_STRUCT_ALIAS (_lowcore, lowcore); /* linux -4.4 */
+ LK_DECLARE_STRUCT_ALIAS (lowcore, lowcore); /* linux 4.5+ */
+ if (LK_STRUCT (lowcore) == NULL)
+ error (_("Could not find struct lowcore. Aborting."));
+ LK_DECLARE_FIELD (lowcore, percpu_offset);
+ LK_DECLARE_FIELD (lowcore, cpu_nr);
+ LK_DECLARE_FIELD (lowcore, async_stack);
+ LK_DECLARE_FIELD (lowcore, panic_stack);
+ LK_DECLARE_FIELD (lowcore, restart_stack);
+
+ LK_DECLARE_FIELD (psw_t, mask);
+ LK_DECLARE_FIELD (psw_t, addr);
+
+ LK_DECLARE_FIELD (mod_arch_specific, plt_offset);
+ LK_DECLARE_FIELD (mod_arch_specific, plt_size);
+
+ LK_DECLARE_ADDR (lowcore_ptr);
+ LK_DECLARE_ADDR (high_memory);
+
+ LK_HOOK->get_registers = s390_lk_get_registers;
+ LK_HOOK->is_kvaddr = s390_lk_is_kvaddr;
+ LK_HOOK->vtop = s390_lk_vtop;
+ LK_HOOK->adjust_module_layout = s390_lk_adjust_module_layout;
+ LK_HOOK->get_percpu_offset = s390_lk_get_percpu_offset;
+ LK_HOOK->map_running_task_to_cpu = s390_lk_map_running_task_to_cpu;
+}
+
+/* Initialize Linux kernel specific gdbarch hooks. */
+
+void
+s390_lk_gdbarch_init (struct gdbarch_info info, struct gdbarch *gdbarch)
+{
+ /* Frame handling. */
+ frame_unwind_append_unwinder (gdbarch, &s390_lk_sigtramp_unwind);
+ frame_unwind_append_unwinder (gdbarch, &s390_lk_frame_unwind);
+
+ /* Support linux kernel debugging. */
+ set_gdbarch_lk_init_private (gdbarch, s390_lk_init_private);
+}
+
+extern initialize_file_ftype _initialize_s390_lk_tdep; /* -Wmissing-prototypes */
+
+void
+_initialize_s390_lk_tdep (void)
+{
+ /* Initialize the Linux kernel target descriptions. */
+ initialize_tdesc_s390x_cr_linux64 ();
+ initialize_tdesc_s390x_vxcr_linux64 ();
+}
diff --git a/gdb/s390-lk-tdep.h b/gdb/s390-lk-tdep.h
new file mode 100644
index 0000000000..4e54e7d236
--- /dev/null
+++ b/gdb/s390-lk-tdep.h
@@ -0,0 +1,54 @@
+/* Target-dependent code for linux-kernel target on S390.
+ 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 S390_LK_TDEP_H
+#define S390_LK_TDEP_H
+
+/* Constants copied from kernel sources. */
+#define S390_LK_PAGESIZE (1UL << 12)
+#define S390_LK_STACKSIZE (S390_LK_PAGESIZE << 2)
+#define S390_LK_ASYNCSIZE (S390_LK_STACKSIZE)
+#define S390_LK_ROUNDUP(val, base) ((val) + (base) - ((val) % (base)))
+
+/* Short hand access to gprs stored in struct pt_regs. */
+#define s390_lk_pt_regs_gpr(regnum) \
+ (LK_OFFSET (pt_regs, gprs) \
+ + (FIELD_TARGET_SIZE (LK_FIELD (pt_regs, gprs)) \
+ * ((regnum) - S390_R0_REGNUM))) \
+
+/* Short hand access to gprs stored in struct stack_frame. */
+#define s390_lk_stack_frame_gpr(regnum) \
+ (LK_OFFSET (stack_frame, gprs) \
+ + (FIELD_TARGET_SIZE (LK_FIELD (stack_frame, gprs)) \
+ * ((regnum) - S390_R6_REGNUM))) \
+
+extern void s390_lk_gdbarch_init (struct gdbarch_info info,
+ struct gdbarch *gdbarch);
+
+/* Core file privileged register sets, defined in s390-lk-tdep.c. */
+extern const struct regset s390x_gregset_lk;
+extern const struct regset s390x_cr_regset;
+extern const struct regset s390x_timer_regset;
+extern const struct regset s390x_todcmp_regset;
+extern const struct regset s390x_todpreg_regset;
+extern const struct regset s390x_prefix_regset;
+
+extern struct target_desc *tdesc_s390x_cr_linux64;
+extern struct target_desc *tdesc_s390x_vxcr_linux64;
+
+#endif /* S390_LK_TDEP_H */
diff --git a/gdb/s390-tdep.c b/gdb/s390-tdep.c
index 7d2906f294..e40fea120a 100644
--- a/gdb/s390-tdep.c
+++ b/gdb/s390-tdep.c
@@ -35,6 +35,7 @@
#include "record-full.h"
#include "reggroups.h"
#include "s390-linux-tdep.h"
+#include "s390-lk-tdep.h"
#include "s390-tdep.h"
#include "target-descriptions.h"
#include "value.h"
@@ -962,7 +963,7 @@ s390_core_read_description (struct gdbarch *gdbarch,
{
asection *section = bfd_get_section_by_name (abfd, ".reg");
CORE_ADDR hwcap = 0;
- int high_gprs, v1, v2, te, vx;
+ int high_gprs, v1, v2, te, vx, cr;

target_auxv_search (target, AT_HWCAP, &hwcap);
if (!section)
@@ -974,6 +975,7 @@ s390_core_read_description (struct gdbarch *gdbarch,
v2 = (bfd_get_section_by_name (abfd, ".reg-s390-system-call") != NULL);
vx = (hwcap & HWCAP_S390_VX);
te = (hwcap & HWCAP_S390_TE);
+ cr = (bfd_get_section_by_name (abfd, ".reg-s390-ctrs") != NULL);

switch (bfd_section_size (abfd, section))
{
@@ -990,6 +992,8 @@ s390_core_read_description (struct gdbarch *gdbarch,

case s390x_sizeof_gregset:
return (te && vx ? tdesc_s390x_tevx_linux64 :
+ vx && cr ? tdesc_s390x_vxcr_linux64 :
+ cr ? tdesc_s390x_cr_linux64 :
vx ? tdesc_s390x_vx_linux64 :
te ? tdesc_s390x_te_linux64 :
v2 ? tdesc_s390x_linux64v2 :
@@ -1046,6 +1050,22 @@ s390_iterate_over_regset_sections (struct gdbarch *gdbarch,
cb (".reg-s390-vxrs-high", 16 * 16, &s390_vxrs_high_regset,
"s390 vector registers 16-31", cb_data);
}
+
+ /* Privileged registers for kernel debugging. */
+ if (bfd_get_section_by_name (core_bfd, ".reg-s390-timer"))
+ cb (".reg-s390-timer", 8, &s390x_timer_regset, "s390 timer", cb_data);
+ if (bfd_get_section_by_name (core_bfd, ".reg-s390-todcmp"))
+ cb (".reg-s390-todcmp", 8, &s390x_todcmp_regset,
+ "s390 clock comperator", cb_data);
+ if (bfd_get_section_by_name (core_bfd, ".reg-s390-todpreg"))
+ cb (".reg-s390-todpreg", 4, &s390x_todpreg_regset,
+ "s390 TOD programable register", cb_data);
+ if (bfd_get_section_by_name (core_bfd, ".reg-s390-ctrs"))
+ cb (".reg-s390-ctrs", 16 * 8, &s390x_cr_regset,
+ "s390 control registers", cb_data);
+ if (bfd_get_section_by_name (core_bfd, ".reg-s390-prefix"))
+ cb (".reg-s390-prefix", 4, &s390x_prefix_regset,
+ "s390 prefix area", cb_data);
}

@@ -2302,6 +2322,7 @@ s390_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
int have_linux_v2 = 0;
int have_tdb = 0;
int have_vx = 0;
+ int have_privileged = 0;
int first_pseudo_reg, last_pseudo_reg;
static const char *const stap_register_prefixes[] = { "%", NULL };
static const char *const stap_register_indirection_prefixes[] = { "(",
@@ -2476,6 +2497,30 @@ s390_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
have_vx = 1;
}

+ /* Control registers. */
+ feature = tdesc_find_feature (tdesc, "org.gnu.gdb.s390.cr");
+ if (feature)
+ {
+ for (i = 0; i < 16; i++)
+ valid_p &= tdesc_numbered_register (feature, tdesc_data,
+ S390_CR0_REGNUM + i, cr[i]);
+ }
+
+ /* Privileged registers. */
+ feature = tdesc_find_feature (tdesc, "org.gnu.gdb.s390.privileged");
+ if (feature)
+ {
+ valid_p &= tdesc_numbered_register (feature, tdesc_data,
+ S390_TIMER_REGNUM, "timer");
+ valid_p &= tdesc_numbered_register (feature, tdesc_data,
+ S390_TODCMP_REGNUM, "todcmp");
+ valid_p &= tdesc_numbered_register (feature, tdesc_data,
+ S390_TODPREG_REGNUM, "todpreg");
+ valid_p &= tdesc_numbered_register (feature, tdesc_data,
+ S390_PREFIX_REGNUM, "prefix");
+ have_privileged = 1;
+ }
+
if (!valid_p)
{
tdesc_data_cleanup (tdesc_data);
@@ -2648,8 +2693,14 @@ s390_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
set_gdbarch_valid_disassembler_options (gdbarch,
disassembler_options_s390 ());

- /* Note that GNU/Linux is the only OS supported on this platform. */
- s390_linux_gdbarch_init (info, gdbarch);
+ /* Note that GNU/Linux is the only OS supported on this platform.
+ Nevertheless we need to distinguish between a kernel- and user-space
+ gdbarch. GDBs osabi support is not sufficient for our case as for both
+ cases the ELF bits are for Linux. Thus fallback to this method. */
+ if (have_privileged)
+ s390_lk_gdbarch_init (info, gdbarch);
+ else
+ s390_linux_gdbarch_init (info, gdbarch);

return gdbarch;
}
--
2.11.2