From patchwork Mon Mar 12 15:31:10 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Philipp Rudo X-Patchwork-Id: 26292 Received: (qmail 5889 invoked by alias); 12 Mar 2018 15:31:42 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 5779 invoked by uid 89); 12 Mar 2018 15:31:41 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-25.0 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_LAZY_DOMAIN_SECURITY, KAM_SHORT, KAM_STOCKGEN, RCVD_IN_DNSWL_LOW autolearn=ham version=3.3.2 spammy= X-HELO: mx0a-001b2d01.pphosted.com Received: from mx0a-001b2d01.pphosted.com (HELO mx0a-001b2d01.pphosted.com) (148.163.156.1) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Mon, 12 Mar 2018 15:31:31 +0000 Received: from pps.filterd (m0098409.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.16.0.22/8.16.0.22) with SMTP id w2CFToYR086027 for ; Mon, 12 Mar 2018 11:31:30 -0400 Received: from e06smtp13.uk.ibm.com (e06smtp13.uk.ibm.com [195.75.94.109]) by mx0a-001b2d01.pphosted.com with ESMTP id 2gnrqfjtwk-1 (version=TLSv1.2 cipher=AES256-SHA256 bits=256 verify=NOT) for ; Mon, 12 Mar 2018 11:31:28 -0400 Received: from localhost by e06smtp13.uk.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Mon, 12 Mar 2018 15:31:24 -0000 Received: from b06cxnps3075.portsmouth.uk.ibm.com (9.149.109.195) by e06smtp13.uk.ibm.com (192.168.101.143) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Mon, 12 Mar 2018 15:31:22 -0000 Received: from d06av21.portsmouth.uk.ibm.com (d06av21.portsmouth.uk.ibm.com [9.149.105.232]) by b06cxnps3075.portsmouth.uk.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id w2CFVLSq62128356; Mon, 12 Mar 2018 15:31:21 GMT Received: from d06av21.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 163E152057; Mon, 12 Mar 2018 14:22:54 +0000 (GMT) Received: from tuxmaker.boeblingen.de.ibm.com (unknown [9.152.85.9]) by d06av21.portsmouth.uk.ibm.com (Postfix) with ESMTPS id AE93A5204D; Mon, 12 Mar 2018 14:22:53 +0000 (GMT) From: Philipp Rudo To: gdb-patches@sourceware.org Cc: Omair Javaid , Yao Qi , arnez@linux.vnet.ibm.com Subject: [RFC PATCH v5 4/9] Add basic Linux kernel support Date: Mon, 12 Mar 2018 16:31:10 +0100 In-Reply-To: <20180312153115.47321-1-prudo@linux.vnet.ibm.com> References: <20180312153115.47321-1-prudo@linux.vnet.ibm.com> X-TM-AS-GCONF: 00 x-cbid: 18031215-0012-0000-0000-000005BC7F7B X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 18031215-0013-0000-0000-00001938AE50 Message-Id: <20180312153115.47321-5-prudo@linux.vnet.ibm.com> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2018-03-12_09:, , signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 malwarescore=0 suspectscore=43 phishscore=0 bulkscore=0 spamscore=0 clxscore=1015 lowpriorityscore=0 impostorscore=0 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1709140000 definitions=main-1803120176 X-IsSubscribed: yes 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 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 . */ + +#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 + /include/linux/types.h. */ + +class lk_bitmap +{ +public: + + template + class base_iterator + : public std::iterator + { + public: + base_iterator (const base_iterator &it) = default; + base_iterator (std::vector::const_iterator start, + size_t bit, size_t size) + : m_start (start), m_bit (bit), m_size (size) + { next (); } + + base_iterator &operator++ () + { m_bit++; return next (); } + + base_iterator operator++ (int) + { base_iterator retval = *this; ++(*this); return retval; } + + base_iterator &operator-- () + { m_bit--; return prev (); } + + base_iterator operator-- (int) + { base_iterator retval = *this; --(*this); return retval; } + + bool operator== (base_iterator other) const + { return (m_start == other.m_start && m_bit == other.m_bit + && m_size == other.m_size); } + + bool operator!= (base_iterator other) const + { return !(*this == other); } + + T operator* () const + { return m_bit; } + + private: + /* Start of the vector containing the bitmap. */ + std::vector::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 &next (); + + /* Get previous set bit. */ + base_iterator &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 iterator; + typedef base_iterator 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 m_bitmap; +}; /* class bitmap */ + +/* see declaration. */ + +template +lk_bitmap::base_iterator & +lk_bitmap::base_iterator::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 +lk_bitmap::base_iterator & +lk_bitmap::base_iterator::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 . */ + +#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 + /include/linux/types.h . */ + +class lk_list +{ + template + class base_iterator + : public std::iterator + { + public: + base_iterator (const base_iterator &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 &operator++ () + { return next (); } + + base_iterator operator++ (int) + { base_iterator retval = *this; ++(*this); return retval; } + + base_iterator &operator-- () + { return next (false); } + + base_iterator operator-- (int) + { base_iterator retval = *this; --(*this); return retval; } + + bool operator== (base_iterator &other) const + { return (m_start == other.m_start && m_current == other.m_current + && !m_first); } + + bool operator!= (base_iterator &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 &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 iterator; + typedef base_iterator 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 +bool +lk_list::base_iterator::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 +lk_list::base_iterator & +lk_list::base_iterator::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 . */ + +#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 + +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 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 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 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 . */ + +#ifndef __LK_LOW_H__ +#define __LK_LOW_H__ + +#include + +#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 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 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 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 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 },