[RFC,v3,4/8] Add kernel module support for linux-kernel target
Commit Message
This patch implements module support for the new linux-kernel target by
adding a target_so_ops. In addition this patch adds handling for kernel
virtual addresses. This is necessary because kernel modules, unlike
task_structs, live in kernel virtual address space. Thus addresses need
to be translated before they can be read from. We achieve this by adding
an implementation for the targets to_xfer_partial hook, which translates
the addresses before passing them down to the target beneath.
gdb/ChangeLog:
* lk-modules.h: New file.
* lk-modules.c: New file.
* lk-low.h (lk_hook_is_kvaddr, lk_hook_vtop)
(lk_hook_get_module_text_offset): New arch dependent hooks.
(sturct lk_private_hooks): Add new hooks.
(LK_MODULES_NAME_LEN, LK_UTS_NAME_LEN): New define.
* lk-low.c (lk-modules.h): New include.
(lk_kvtop, restore_current_target, lk_xfer_partial): New functions.
(lk_init_private_data): Declare needed debug symbols.
(lk_try_push_target): Assert for new hooks and set solib_ops.
(init_linux_kernel_ops): Add implementation for to_xfer_partial.
* solib.c (get_solib_search_path): New function.
* solib.h (get_solib_search_path): New export.
* Makefile.in (SFILES, ALLDEPFILES): Add lk-modules.c.
(HFILES_NO_SRCDIR): Add lk-modules.h.
(ALL_TARGET_OBS): Add lk-modules.o.
* configure.tgt (lk_target_obs): Add lk-modules.o.
---
gdb/Makefile.in | 4 +
gdb/configure.tgt | 2 +-
gdb/lk-low.c | 101 +++++++++++++
gdb/lk-low.h | 24 ++++
gdb/lk-modules.c | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
gdb/lk-modules.h | 29 ++++
gdb/solib.c | 8 ++
gdb/solib.h | 5 +
8 files changed, 584 insertions(+), 1 deletion(-)
create mode 100644 gdb/lk-modules.c
create mode 100644 gdb/lk-modules.h
Comments
Philipp Rudo <prudo@linux.vnet.ibm.com> writes:
> +/* Translate a kernel virtual address ADDR to a physical address. */
> +
> +CORE_ADDR
> +lk_kvtop (CORE_ADDR addr)
How about lk_kernel_vir_to_phy_addr?
> +{
> + CORE_ADDR pgd = lk_read_addr (LK_ADDR (init_mm)
> + + LK_OFFSET (mm_struct, pgd));
> + return LK_HOOK->vtop (pgd, addr);
> +}
> +
> +/* Restore current_target to TARGET. */
> +static void
> +restore_current_target (void *target)
> +{
> + current_target.beneath = (struct target_ops *) target;
> +}
> +
> +/* 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;
> + struct cleanup *old_chain = make_cleanup (restore_current_target,
> ops);
Use make_scoped_restore instead of make_cleanup?
> +
> + current_target.beneath = ops->beneath;
> +
Any reasons you switch current_target.beneath temporarily?
> + if (LK_HOOK->is_kvaddr (offset))
> + offset = lk_kvtop (offset);
> +
> + ret_val = ops->beneath->to_xfer_partial (ops->beneath, object, annex,
> + readbuf, writebuf, offset, len,
> + xfered_len);
Two spaces after "=".
> diff --git a/gdb/lk-modules.c b/gdb/lk-modules.c
> new file mode 100644
> index 0000000..f3c559d
> --- /dev/null
> +++ b/gdb/lk-modules.c
> @@ -0,0 +1,412 @@
> +/* 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-lists.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>
> +#include <string>
> +
> +struct target_so_ops *lk_modules_so_ops = NULL;
> +
> +/* Info for single section type. */
> +
> +struct lm_info_sec
> +{
> + CORE_ADDR start;
> + CORE_ADDR offset;
> + unsigned int size;
> +};
> +
> +/* Link map info to include in an allocated so_list entry. */
> +
> +struct lm_info
> +{
> + CORE_ADDR base;
> + unsigned int size;
> +
> + struct lm_info_sec text;
> + struct lm_info_sec init_text;
> + struct lm_info_sec ro_data;
> + struct lm_info_sec rw_data;
> + struct lm_info_sec percpu;
> +};
Comments to these fields are needed.
> +
> +/* 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. */
> +
> +std::unordered_map<std::string, std::string>
> +lk_modules_build_path_map ()
> +{
> + std::unordered_map<std::string, std::string> umap;
> + FILE *mod_order;
> + struct cleanup *old_chain;
> + char line[SO_NAME_MAX_PATH_SIZE + 1];
> +
> + mod_order = lk_modules_open_mod_order ();
> + old_chain = make_cleanup_fclose (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))
> + {
> + /* 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);
> + }
> +
> + do_cleanups (old_chain);
> + return umap;
> +}
> +
> +/* Allocate and fill a copy of struct lm_info for module at address MOD. */
> +
> +struct lm_info *
> +lk_modules_read_lm_info (CORE_ADDR mod)
> +{
> + struct lm_info *lmi = XNEW (struct lm_info);
> + struct cleanup *old_chain = make_cleanup (xfree, lmi);
> +
use std::unique_ptr to avoid cleanup.
sdt::unique_ptr<lm_info> lmi (new lm_info ());
> + if (LK_FIELD (module, module_core)) /* linux -4.4 */
> + {
> + lmi->base = lk_read_addr (mod + LK_OFFSET (module, module_core));
> + lmi->size = lk_read_addr (mod + LK_OFFSET (module, core_size));
> +
> + lmi->text.start = lmi->base;
> + lmi->text.offset = LK_HOOK->get_module_text_offset (mod);
> + lmi->text.size = lk_read_uint (mod + LK_OFFSET (module, core_text_size));
> +
> + lmi->ro_data.start = lmi->base + lmi->text.size;
> + lmi->ro_data.offset = 0;
> + lmi->ro_data.size = lk_read_uint (mod + LK_OFFSET (module,
> + core_ro_size));
> + }
> + else /* linux 4.5+ */
> + {
> + CORE_ADDR mod_core = mod + LK_OFFSET (module, core_layout);
> +
> + lmi->base = lk_read_addr (mod_core
> + + LK_OFFSET (module_layout, base));
> + lmi->size = lk_read_uint (mod_core
> + + LK_OFFSET (module_layout, size));
> +
> + lmi->text.start = lmi->base;
> + lmi->text.offset = LK_HOOK->get_module_text_offset (mod);
> + lmi->text.size = lk_read_uint (mod_core
> + + LK_OFFSET (module_layout, text_size));
> +
> + lmi->ro_data.start = lmi->base + lmi->text.size;
> + lmi->ro_data.offset = 0;
> + lmi->ro_data.size = lk_read_uint (mod_core
> + + LK_OFFSET (module_layout, ro_size));
> + }
> +
> + lmi->rw_data.start = lmi->base + lmi->ro_data.size;
> + lmi->rw_data.offset = 0;
> + lmi->rw_data.size = lmi->size - lmi->ro_data.size;
> +
> + lmi->init_text.start = lk_read_addr (mod + LK_OFFSET (module, init));
> + lmi->init_text.offset = 0;
> +
> + lmi->percpu.start = lk_read_addr (mod + LK_OFFSET (module, percpu));
> + lmi->percpu.size = lk_read_uint (mod + LK_OFFSET (module, percpu_size));
> + lmi->percpu.offset = 0;
> +
> + discard_cleanups (old_chain);
> + return lmi;
> +}
> +
Hi,
On Tue, 02 May 2017 14:15:43 +0100
Yao Qi <qiyaoltc@gmail.com> wrote:
> Philipp Rudo <prudo@linux.vnet.ibm.com> writes:
>
> > +/* Translate a kernel virtual address ADDR to a physical address. */
> > +
> > +CORE_ADDR
> > +lk_kvtop (CORE_ADDR addr)
>
> How about lk_kernel_vir_to_phy_addr?
I prefer kvtop. It's much shorter and (for my taste) is just as readable. But
I don't insist on keeping the name. Are there other opinions?
>
> > +{
> > + CORE_ADDR pgd = lk_read_addr (LK_ADDR (init_mm)
> > + + LK_OFFSET (mm_struct, pgd));
> > + return LK_HOOK->vtop (pgd, addr);
> > +}
> > +
> > +/* Restore current_target to TARGET. */
> > +static void
> > +restore_current_target (void *target)
> > +{
> > + current_target.beneath = (struct target_ops *) target;
> > +}
> > +
> > +/* 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;
> > + struct cleanup *old_chain = make_cleanup (restore_current_target,
> > ops);
>
> Use make_scoped_restore instead of make_cleanup?
Using a scoped_restore probably makes sense. Although I don't see the
advantage over old style cleanups other than having marginally shorter code ...
> > +
> > + current_target.beneath = ops->beneath;
> > +
>
> Any reasons you switch current_target.beneath temporarily?
Yes. lk_kvtop (at least for s390) reads memory if the address is not
physical. Thus reading a virtual address calls xfer_partial twice. Once for
the actual address and a second time for the data lk_kvtop needs. This can
lead to an endless recursion if there is a bug or memory corruption. Switching
to the target beneath prevents this.
> > + if (LK_HOOK->is_kvaddr (offset))
> > + offset = lk_kvtop (offset);
> > +
> > + ret_val = ops->beneath->to_xfer_partial (ops->beneath, object, annex,
> > + readbuf, writebuf, offset, len,
> > + xfered_len);
>
> Two spaces after "=".
Fixed, thanks.
> > diff --git a/gdb/lk-modules.c b/gdb/lk-modules.c
> > new file mode 100644
> > index 0000000..f3c559d
> > --- /dev/null
> > +++ b/gdb/lk-modules.c
> > @@ -0,0 +1,412 @@
> > +/* 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-lists.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>
> > +#include <string>
> > +
> > +struct target_so_ops *lk_modules_so_ops = NULL;
> > +
> > +/* Info for single section type. */
> > +
> > +struct lm_info_sec
> > +{
> > + CORE_ADDR start;
> > + CORE_ADDR offset;
> > + unsigned int size;
> > +};
> > +
> > +/* Link map info to include in an allocated so_list entry. */
> > +
> > +struct lm_info
> > +{
> > + CORE_ADDR base;
> > + unsigned int size;
> > +
> > + struct lm_info_sec text;
> > + struct lm_info_sec init_text;
> > + struct lm_info_sec ro_data;
> > + struct lm_info_sec rw_data;
> > + struct lm_info_sec percpu;
> > +};
>
> Comments to these fields are needed.
Yes, you are right. I will fix it when I adjust to the classification of
lm_info.
> > +
> > +/* 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. */
> > +
> > +std::unordered_map<std::string, std::string>
> > +lk_modules_build_path_map ()
> > +{
> > + std::unordered_map<std::string, std::string> umap;
> > + FILE *mod_order;
> > + struct cleanup *old_chain;
> > + char line[SO_NAME_MAX_PATH_SIZE + 1];
> > +
> > + mod_order = lk_modules_open_mod_order ();
> > + old_chain = make_cleanup_fclose (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))
> > + {
> > + /* 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);
> > + }
> > +
> > + do_cleanups (old_chain);
> > + return umap;
> > +}
> > +
> > +/* Allocate and fill a copy of struct lm_info for module at address MOD.
> > */ +
> > +struct lm_info *
> > +lk_modules_read_lm_info (CORE_ADDR mod)
> > +{
> > + struct lm_info *lmi = XNEW (struct lm_info);
> > + struct cleanup *old_chain = make_cleanup (xfree, lmi);
> > +
>
> use std::unique_ptr to avoid cleanup.
>
> sdt::unique_ptr<lm_info> lmi (new lm_info ());
Yes, a unique_ptr is better. There are probably many cleanups I can get rid
off with the latest C++-yfication.
The only problem I currently have is that the C++-yfication rate is too high I
can barely follow and add new features to have a reliable kernel debugger.
Thanks for the hint anyway.
> > + if (LK_FIELD (module, module_core)) /* linux -4.4 */
> > + {
> > + lmi->base = lk_read_addr (mod + LK_OFFSET (module, module_core));
> > + lmi->size = lk_read_addr (mod + LK_OFFSET (module, core_size));
> > +
> > + lmi->text.start = lmi->base;
> > + lmi->text.offset = LK_HOOK->get_module_text_offset (mod);
> > + lmi->text.size = lk_read_uint (mod + LK_OFFSET (module,
> > core_text_size)); +
> > + lmi->ro_data.start = lmi->base + lmi->text.size;
> > + lmi->ro_data.offset = 0;
> > + lmi->ro_data.size = lk_read_uint (mod + LK_OFFSET (module,
> > + core_ro_size));
> > + }
> > + else /* linux 4.5+ */
> > + {
> > + CORE_ADDR mod_core = mod + LK_OFFSET (module, core_layout);
> > +
> > + lmi->base = lk_read_addr (mod_core
> > + + LK_OFFSET (module_layout, base));
> > + lmi->size = lk_read_uint (mod_core
> > + + LK_OFFSET (module_layout, size));
> > +
> > + lmi->text.start = lmi->base;
> > + lmi->text.offset = LK_HOOK->get_module_text_offset (mod);
> > + lmi->text.size = lk_read_uint (mod_core
> > + + LK_OFFSET (module_layout,
> > text_size)); +
> > + lmi->ro_data.start = lmi->base + lmi->text.size;
> > + lmi->ro_data.offset = 0;
> > + lmi->ro_data.size = lk_read_uint (mod_core
> > + + LK_OFFSET (module_layout,
> > ro_size));
> > + }
> > +
> > + lmi->rw_data.start = lmi->base + lmi->ro_data.size;
> > + lmi->rw_data.offset = 0;
> > + lmi->rw_data.size = lmi->size - lmi->ro_data.size;
> > +
> > + lmi->init_text.start = lk_read_addr (mod + LK_OFFSET (module, init));
> > + lmi->init_text.offset = 0;
> > +
> > + lmi->percpu.start = lk_read_addr (mod + LK_OFFSET (module, percpu));
> > + lmi->percpu.size = lk_read_uint (mod + LK_OFFSET (module, percpu_size));
> > + lmi->percpu.offset = 0;
> > +
> > + discard_cleanups (old_chain);
> > + return lmi;
> > +}
> > +
>
Philipp Rudo <prudo@linux.vnet.ibm.com> writes:
>> > +/* Translate a kernel virtual address ADDR to a physical address. */
>> > +
>> > +CORE_ADDR
>> > +lk_kvtop (CORE_ADDR addr)
>>
>> How about lk_kernel_vir_to_phy_addr?
>
> I prefer kvtop. It's much shorter and (for my taste) is just as readable. But
> I don't insist on keeping the name. Are there other opinions?
>
or maybe lk_vir_to_phy?
>>
>> > +{
>> > + CORE_ADDR pgd = lk_read_addr (LK_ADDR (init_mm)
>> > + + LK_OFFSET (mm_struct, pgd));
>> > + return LK_HOOK->vtop (pgd, addr);
>> > +}
>> > +
>> > +/* Restore current_target to TARGET. */
>> > +static void
>> > +restore_current_target (void *target)
>> > +{
>> > + current_target.beneath = (struct target_ops *) target;
>> > +}
>> > +
>> > +/* 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;
>> > + struct cleanup *old_chain = make_cleanup (restore_current_target,
>> > ops);
>>
>> Use make_scoped_restore instead of make_cleanup?
>
> Using a scoped_restore probably makes sense. Although I don't see the
> advantage over old style cleanups other than having marginally shorter code ...
>
We want to reduce the usages of cleanup, and even completely remove it
ultimately, so we should avoid using it in new code.
>> > +
>> > + current_target.beneath = ops->beneath;
>> > +
>>
>> Any reasons you switch current_target.beneath temporarily?
>
> Yes. lk_kvtop (at least for s390) reads memory if the address is not
> physical. Thus reading a virtual address calls xfer_partial twice. Once for
> the actual address and a second time for the data lk_kvtop needs. This can
> lead to an endless recursion if there is a bug or memory corruption. Switching
> to the target beneath prevents this.
>
Does it work if you pass ops->beneath to lk_kvtop and all lk_read_XXX
apis, so that we can use ops->beneath there instead of current_target.
Hi
On Fri, 05 May 2017 22:33:51 +0100
Yao Qi <qiyaoltc@gmail.com> wrote:
> Philipp Rudo <prudo@linux.vnet.ibm.com> writes:
>
> >> > +/* Translate a kernel virtual address ADDR to a physical address. */
> >> > +
> >> > +CORE_ADDR
> >> > +lk_kvtop (CORE_ADDR addr)
> >>
> >> How about lk_kernel_vir_to_phy_addr?
> >
> > I prefer kvtop. It's much shorter and (for my taste) is just as readable.
> > But I don't insist on keeping the name. Are there other opinions?
> >
>
> or maybe lk_vir_to_phy?
What about lk_virt_to_phys and lk_kvirt_to_phys? For the general virtual to
physical translation and the special case when the kernel page tables are used.
>
> >>
> >> > +{
> >> > + CORE_ADDR pgd = lk_read_addr (LK_ADDR (init_mm)
> >> > + + LK_OFFSET (mm_struct, pgd));
> >> > + return LK_HOOK->vtop (pgd, addr);
> >> > +}
> >> > +
> >> > +/* Restore current_target to TARGET. */
> >> > +static void
> >> > +restore_current_target (void *target)
> >> > +{
> >> > + current_target.beneath = (struct target_ops *) target;
> >> > +}
> >> > +
> >> > +/* 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;
> >> > + struct cleanup *old_chain = make_cleanup (restore_current_target,
> >> > ops);
> >>
> >> Use make_scoped_restore instead of make_cleanup?
> >
> > Using a scoped_restore probably makes sense. Although I don't see the
> > advantage over old style cleanups other than having marginally shorter
> > code ...
>
> We want to reduce the usages of cleanup, and even completely remove it
> ultimately, so we should avoid using it in new code.
Yes, I understand. It's just that you replace one mechanism to restore the
global variables with an other instead of removing the root cause, global
variables...
> >> > +
> >> > + current_target.beneath = ops->beneath;
> >> > +
> >>
> >> Any reasons you switch current_target.beneath temporarily?
> >
> > Yes. lk_kvtop (at least for s390) reads memory if the address is not
> > physical. Thus reading a virtual address calls xfer_partial twice. Once
> > for the actual address and a second time for the data lk_kvtop needs. This
> > can lead to an endless recursion if there is a bug or memory corruption.
> > Switching to the target beneath prevents this.
> >
>
> Does it work if you pass ops->beneath to lk_kvtop and all lk_read_XXX
> apis, so that we can use ops->beneath there instead of current_target.
Well switching the target is 'just' a backup. Only a few lines below in
lk_xfer_partial I do this
ret_val = ops->beneath->to_xfer_partial (ops->beneath, object, annex,
readbuf, writebuf, offset, len,
xfered_len);
Switching the target assures that, if ops->beneath->to_xfer_partial calls other
target methods via the global current_target those methods also belong to
ops->beneath. Moving this to lk_kvtop or lk_read_* wouldn't prevent the
problem but would lead to code duplication.
Thanks
Philipp
Philipp Rudo <prudo@linux.vnet.ibm.com> writes:
>> or maybe lk_vir_to_phy?
>
> What about lk_virt_to_phys and lk_kvirt_to_phys? For the general virtual to
> physical translation and the special case when the kernel page tables are used.
>
lk_virt_to_phys is good to me.
@@ -819,6 +819,7 @@ ALL_TARGET_OBS = \
linux-tdep.o \
lk-lists.o \
lk-low.o \
+ lk-modules.o \
lm32-tdep.o \
m32c-tdep.o \
m32r-linux-tdep.o \
@@ -1107,6 +1108,7 @@ SFILES = \
linespec.c \
lk-lists.c \
lk-low.c \
+ lk-modules.c \
location.c \
m2-exp.y \
m2-lang.c \
@@ -1356,6 +1358,7 @@ HFILES_NO_SRCDIR = \
linux-tdep.h \
lk-lists.h \
lk-low.h \
+ lk-modules.h \
location.h \
m2-lang.h \
m32r-tdep.h \
@@ -2555,6 +2558,7 @@ ALLDEPFILES = \
linux-tdep.c \
lk-lists.c \
lk-low.c \
+ lk-modules.c \
lm32-tdep.c \
m32r-linux-nat.c \
m32r-linux-tdep.c \
@@ -36,7 +36,7 @@ esac
# List of objectfiles for Linux kernel support. To be included into *-linux*
# targets wich support Linux kernel debugging.
-lk_target_obs="lk-lists.o lk-low.o"
+lk_target_obs="lk-lists.o lk-low.o lk-modules.o"
# map target info into gdb names.
@@ -29,6 +29,7 @@
#include "inferior.h"
#include "lk-lists.h"
#include "lk-low.h"
+#include "lk-modules.h"
#include "objfiles.h"
#include "observer.h"
#include "solib.h"
@@ -536,6 +537,46 @@ lk_thread_name (struct target_ops *target, struct thread_info *ti)
return buf;
}
+/* Translate a kernel virtual address ADDR to a physical address. */
+
+CORE_ADDR
+lk_kvtop (CORE_ADDR addr)
+{
+ CORE_ADDR pgd = lk_read_addr (LK_ADDR (init_mm)
+ + LK_OFFSET (mm_struct, pgd));
+ return LK_HOOK->vtop (pgd, addr);
+}
+
+/* Restore current_target to TARGET. */
+static void
+restore_current_target (void *target)
+{
+ current_target.beneath = (struct target_ops *) target;
+}
+
+/* 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;
+ struct cleanup *old_chain = make_cleanup (restore_current_target, ops);
+
+ current_target.beneath = ops->beneath;
+
+ if (LK_HOOK->is_kvaddr (offset))
+ offset = lk_kvtop (offset);
+
+ ret_val = ops->beneath->to_xfer_partial (ops->beneath, object, annex,
+ readbuf, writebuf, offset, len,
+ xfered_len);
+ do_cleanups (old_chain);
+ 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. */
@@ -571,6 +612,9 @@ lk_init_private ()
/* Initialize architecture independent private data. Must be called
_after_ symbol tables were initialized. */
+/* FIXME: throw error more fine-grained. */
+/* FIXME: make independent of compile options. */
+
static void
lk_init_private_data ()
{
@@ -591,10 +635,61 @@ lk_init_private_data ()
LK_DECLARE_FIELD (cpumask, bits);
+ LK_DECLARE_FIELD (mm_struct, pgd);
+
+ LK_DECLARE_FIELD (pgd_t, pgd);
+
+ LK_DECLARE_FIELD (module, list);
+ LK_DECLARE_FIELD (module, name);
+ LK_DECLARE_FIELD (module, source_list);
+ LK_DECLARE_FIELD (module, arch);
+ LK_DECLARE_FIELD (module, init);
+ LK_DECLARE_FIELD (module, percpu);
+ LK_DECLARE_FIELD (module, percpu_size);
+
+ /* Module offset moved to new struct module_layout with linux 4.5.
+ It must be checked in code which of this fields exist. */
+ if (LK_DECLARE_FIELD_SILENT (module_layout, base)) /* linux 4.5+ */
+ {
+ LK_DECLARE_FIELD (module, init_layout);
+ LK_DECLARE_FIELD (module, core_layout);
+
+ LK_DECLARE_FIELD (module_layout, size);
+ LK_DECLARE_FIELD (module_layout, text_size);
+ LK_DECLARE_FIELD (module_layout, ro_size);
+ }
+ else if (LK_DECLARE_FIELD_SILENT (module, module_core)) /* linux -4.4 */
+ {
+ LK_DECLARE_FIELD (module, init_size);
+ LK_DECLARE_FIELD (module, core_size);
+
+ LK_DECLARE_FIELD (module, core_text_size);
+ LK_DECLARE_FIELD (module, core_ro_size);
+ }
+ else
+ {
+ error (_("Could not find module base. Aborting."));
+ }
+
+ LK_DECLARE_FIELD (module_use, source_list);
+ LK_DECLARE_FIELD (module_use, source);
+
+ LK_DECLARE_FIELD (uts_namespace, name);
+
+ LK_DECLARE_STRUCT_ALIAS (new_utsname, utsname);
+ LK_DECLARE_STRUCT_ALIAS (old_utsname, utsname);
+ LK_DECLARE_STRUCT_ALIAS (oldold_utsname, utsname);
+ if (LK_STRUCT (utsname) == NULL)
+ error (_("Could not find struct utsname. Aborting."));
+ LK_DECLARE_FIELD (utsname, version);
+ LK_DECLARE_FIELD (utsname, release);
+
LK_DECLARE_ADDR (init_task);
LK_DECLARE_ADDR (runqueues);
LK_DECLARE_ADDR (__per_cpu_offset);
LK_DECLARE_ADDR (init_mm);
+ LK_DECLARE_ADDR (modules);
+ LK_DECLARE_ADDR (init_uts_ns);
LK_DECLARE_ADDR_ALIAS (__cpu_online_mask, cpu_online_mask); /* linux 4.5+ */
LK_DECLARE_ADDR_ALIAS (cpu_online_bits, cpu_online_mask); /* linux -4.4 */
@@ -693,12 +788,17 @@ lk_try_push_target ()
gdbarch_lk_init_private (gdbarch);
/* Check for required arch hooks. */
gdb_assert (LK_HOOK->get_registers);
+ gdb_assert (LK_HOOK->is_kvaddr);
+ gdb_assert (LK_HOOK->vtop);
+ gdb_assert (LK_HOOK->get_module_text_offset);
lk_init_ptid_map ();
lk_update_thread_list (linux_kernel_ops);
if (!target_is_pushed (linux_kernel_ops))
push_target (linux_kernel_ops);
+
+ set_solib_ops (gdbarch, lk_modules_so_ops);
}
/* Function for targets to_open hook. */
@@ -811,6 +911,7 @@ init_linux_kernel_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;
@@ -27,6 +27,8 @@ extern struct target_ops *linux_kernel_ops;
/* Copy 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
@@ -204,6 +206,19 @@ typedef void (*lk_hook_get_registers) (CORE_ADDR task,
struct regcache *regcache,
int regnum);
+/* Hook to check if address ADDR is a kernel virtual address.
+ NOTE: This hook is called in the context of target beneath. */
+typedef int (*lk_hook_is_kvaddr) (CORE_ADDR addr);
+
+/* Hook to 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. */
+typedef CORE_ADDR (*lk_hook_vtop) (CORE_ADDR addr, CORE_ADDR pgd);
+
+/* Hook to get the offset between a modules base and the start of its
+ .text section. */
+typedef CORE_ADDR (*lk_hook_get_module_text_offset) (CORE_ADDR mod);
+
/* Hook to return the per_cpu_offset of cpu CPU. Only architectures that
do not use the __per_cpu_offset array to determin the offset have to
supply this hook. */
@@ -218,6 +233,15 @@ struct lk_private_hooks
/* required */
lk_hook_get_registers get_registers;
+ /* required */
+ lk_hook_is_kvaddr is_kvaddr;
+
+ /* required */
+ lk_hook_vtop vtop;
+
+ /* reqired */
+ lk_hook_get_module_text_offset get_module_text_offset;
+
/* optional, required if __per_cpu_offset array is not used to determine
offset. */
lk_hook_get_percpu_offset get_percpu_offset;
new file mode 100644
@@ -0,0 +1,412 @@
+/* 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-lists.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>
+#include <string>
+
+struct target_so_ops *lk_modules_so_ops = NULL;
+
+/* Info for single section type. */
+
+struct lm_info_sec
+{
+ CORE_ADDR start;
+ CORE_ADDR offset;
+ unsigned int size;
+};
+
+/* Link map info to include in an allocated so_list entry. */
+
+struct lm_info
+{
+ CORE_ADDR base;
+ unsigned int size;
+
+ struct lm_info_sec text;
+ struct lm_info_sec init_text;
+ struct lm_info_sec ro_data;
+ struct lm_info_sec rw_data;
+ struct lm_info_sec percpu;
+};
+
+/* Check if debug info for module NAME are loaded. */
+
+bool
+lk_modules_debug_info_loaded (const std::string &name)
+{
+ struct so_list *so;
+
+ for (so = master_so_list (); so; 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_ADDR (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. */
+
+FILE *
+lk_modules_open_mod_order ()
+{
+ FILE *mod_order;
+ std::string filename = concat_path (lk_modules_expand_search_path (),
+ "modules.order");
+ 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. */
+
+std::unordered_map<std::string, std::string>
+lk_modules_build_path_map ()
+{
+ std::unordered_map<std::string, std::string> umap;
+ FILE *mod_order;
+ struct cleanup *old_chain;
+ char line[SO_NAME_MAX_PATH_SIZE + 1];
+
+ mod_order = lk_modules_open_mod_order ();
+ old_chain = make_cleanup_fclose (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))
+ {
+ /* 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);
+ }
+
+ do_cleanups (old_chain);
+ return umap;
+}
+
+/* Allocate and fill a copy of struct lm_info for module at address MOD. */
+
+struct lm_info *
+lk_modules_read_lm_info (CORE_ADDR mod)
+{
+ struct lm_info *lmi = XNEW (struct lm_info);
+ struct cleanup *old_chain = make_cleanup (xfree, lmi);
+
+ if (LK_FIELD (module, module_core)) /* linux -4.4 */
+ {
+ lmi->base = lk_read_addr (mod + LK_OFFSET (module, module_core));
+ lmi->size = lk_read_addr (mod + LK_OFFSET (module, core_size));
+
+ lmi->text.start = lmi->base;
+ lmi->text.offset = LK_HOOK->get_module_text_offset (mod);
+ lmi->text.size = lk_read_uint (mod + LK_OFFSET (module, core_text_size));
+
+ lmi->ro_data.start = lmi->base + lmi->text.size;
+ lmi->ro_data.offset = 0;
+ lmi->ro_data.size = lk_read_uint (mod + LK_OFFSET (module,
+ core_ro_size));
+ }
+ else /* linux 4.5+ */
+ {
+ CORE_ADDR mod_core = mod + LK_OFFSET (module, core_layout);
+
+ lmi->base = lk_read_addr (mod_core
+ + LK_OFFSET (module_layout, base));
+ lmi->size = lk_read_uint (mod_core
+ + LK_OFFSET (module_layout, size));
+
+ lmi->text.start = lmi->base;
+ lmi->text.offset = LK_HOOK->get_module_text_offset (mod);
+ lmi->text.size = lk_read_uint (mod_core
+ + LK_OFFSET (module_layout, text_size));
+
+ lmi->ro_data.start = lmi->base + lmi->text.size;
+ lmi->ro_data.offset = 0;
+ lmi->ro_data.size = lk_read_uint (mod_core
+ + LK_OFFSET (module_layout, ro_size));
+ }
+
+ lmi->rw_data.start = lmi->base + lmi->ro_data.size;
+ lmi->rw_data.offset = 0;
+ lmi->rw_data.size = lmi->size - lmi->ro_data.size;
+
+ lmi->init_text.start = lk_read_addr (mod + LK_OFFSET (module, init));
+ lmi->init_text.offset = 0;
+
+ lmi->percpu.start = lk_read_addr (mod + LK_OFFSET (module, percpu));
+ lmi->percpu.size = lk_read_uint (mod + LK_OFFSET (module, percpu_size));
+ lmi->percpu.offset = 0;
+
+ discard_cleanups (old_chain);
+ return lmi;
+}
+
+/* Function for current_sos hook. */
+
+struct so_list *
+lk_modules_current_sos (void)
+{
+ CORE_ADDR modules, next;
+ FILE *mod_order;
+ struct so_list *list = NULL;
+ std::unordered_map<std::string, std::string> umap;
+
+ umap = lk_modules_build_path_map ();
+ modules = LK_ADDR (modules);
+ lk_list_for_each (next, modules, module, list)
+ {
+ char name[LK_MODULE_NAME_LEN];
+ CORE_ADDR mod, name_addr;
+
+ mod = LK_CONTAINER_OF (next, module, list);
+ name_addr = mod + LK_OFFSET (module, name);
+ read_memory_string (name_addr, name, LK_MODULE_NAME_LEN);
+
+ if (umap.count (name))
+ {
+ struct so_list *newso = XCNEW (struct so_list);
+
+ newso->next = list;
+ list = newso;
+ newso->lm_info = lk_modules_read_lm_info (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 SEC to section type LMI_SEC. Helper function for
+ lk_modules_relocate_section_addresses. */
+
+void
+lk_modules_relocate_sec (struct target_section *sec,
+ struct lm_info_sec *lmi_sec)
+{
+ unsigned int alignment = 1;
+
+ alignment = 1 << sec->the_bfd_section->alignment_power;
+
+ /* Adjust offset to section alignment. */
+ if (lmi_sec->offset % alignment != 0)
+ lmi_sec->offset += alignment - (lmi_sec->offset % alignment);
+
+ sec->addr += lmi_sec->start + lmi_sec->offset;
+ sec->endaddr += lmi_sec->start + lmi_sec->offset;
+ lmi_sec->offset += sec->endaddr - sec->addr;
+}
+
+/* Function for relocate_section_addresses hook. */
+
+void
+lk_modules_relocate_section_addresses (struct so_list *so,
+ struct target_section *sec)
+{
+ struct lm_info *lmi = so->lm_info;
+ unsigned int flags = sec->the_bfd_section->flags;
+ const char *name = sec->the_bfd_section->name;
+
+ if (streq (name, ".modinfo") || streq (name, "__versions"))
+ return;
+
+ /* FIXME: Make dependent on module state, i.e. only map .init sections if
+ * state is MODULE_STATE_COMING. */
+ if (startswith (name, ".init"))
+ lk_modules_relocate_sec (sec, &lmi->init_text);
+ else if (endswith (name, ".percpu"))
+ lk_modules_relocate_sec (sec, &lmi->percpu);
+ else if (flags & SEC_CODE)
+ lk_modules_relocate_sec (sec, &lmi->text);
+ else if (flags & SEC_READONLY)
+ lk_modules_relocate_sec (sec, &lmi->ro_data);
+ else if (flags & SEC_ALLOC)
+ lk_modules_relocate_sec (sec, &lmi->rw_data);
+
+ /* Set address range to be displayed with info shared.
+ size = text + (ro + rw) data without .init sections. */
+ if (so->addr_low == so->addr_high)
+ {
+ so->addr_low = lmi->base;
+ so->addr_high = lmi->base + lmi->size;
+ }
+}
+
+/* Function for free_so hook. */
+
+void
+lk_modules_free_so (struct so_list *so)
+{
+ xfree (so->lm_info);
+}
+
+/* Function for clear_so hook. */
+
+void
+lk_modules_clear_so (struct so_list *so)
+{
+ if (so->lm_info != NULL)
+ memset (so->lm_info, 0, sizeof (struct lm_info));
+}
+
+/* Function for clear_solib hook. */
+
+void
+lk_modules_clear_solib ()
+{
+ /* Nothing to do. */
+}
+
+/* Function for clear_create_inferior_hook hook. */
+
+void
+lk_modules_create_inferior_hook (int from_tty)
+{
+ /* Nothing to do. */
+}
+
+/* Function for clear_create_inferior_hook hook. */
+
+int
+lk_modules_in_dynsym_resolve_code (CORE_ADDR pc)
+{
+ return 0;
+}
+
+/* Function for same hook. */
+
+int
+lk_modules_same (struct so_list *gdb, struct so_list *inf)
+{
+ return streq (gdb->so_name, inf->so_name);
+}
+
+/* Initialize linux modules solib target. */
+
+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 ();
+}
new file mode 100644
@@ -0,0 +1,29 @@
+/* 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__
+
+extern struct target_so_ops *lk_modules_so_ops;
+
+/* Check if debug info for module NAME are loaded. Needed by lsmod command. */
+
+extern bool lk_modules_debug_info_loaded (const std::string &name);
+
+#endif /* __LK_MODULES_H__ */
@@ -107,6 +107,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
@@ -28,6 +28,11 @@ struct program_space;
#include "symfile-add-flags.h"
+/* 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. */