Skip to content

Commit

Permalink
gdbremote: Initial (and minimal) support for remote debugging
Browse files Browse the repository at this point in the history
Both usage and limitations are described in docs/advanced_usage.rst.

Testing is been modest but does follow pretty much all the new code
paths:

1. the reported register values were compared between gdb and drgn
2. frame pointer based (fallback) stack tracing
3. x0 (argc) and x1 (argv) were checked and the pointers chased
   to verify that argv[0] contains the right value

The autotests are based on #1 and #2 above (there are enough memory
reads during a stack trace to exercise the memory read paths).

Signed-off-by: Daniel Thompson <[email protected]>
  • Loading branch information
daniel-thompson committed Oct 14, 2024
1 parent ad9b241 commit b81f461
Show file tree
Hide file tree
Showing 14 changed files with 917 additions and 7 deletions.
13 changes: 13 additions & 0 deletions _drgn.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,14 @@ class Program:
"""
...

def set_gdbremote(self, conn: str) -> None:
"""
Set the program to the specificed elffile and connect to a gdbserver.
:param conn: gdb connection string (e.g. localhost:2345)
"""
...

def set_kernel(self) -> None:
"""
Set the program to the running operating system kernel.
Expand Down Expand Up @@ -1067,6 +1075,11 @@ class ProgramFlags(enum.Flag):
The program is running on the local machine.
"""

IS_GDBREMOTE = ...
"""
The program is connected via the gdbremote protocol.
"""

class FindObjectFlags(enum.Flag):
"""
``FindObjectFlags`` are flags for :meth:`Program.object()`. These can be
Expand Down
49 changes: 49 additions & 0 deletions docs/advanced_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,52 @@ core dumps. These special objects include:
distinguish it from the kernel variable ``vmcoreinfo_data``.

This is available without debugging information.

Debugging via the gdbremote protocol
------------------------------------

The
`gdbremote protocol <https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html>`_
makes it possible to run drgn on one machine and use it to debug code running
on another system. drgn implements the client side of the protocol and can
connect via gdbremote to a variety of different gdbremote "server"
implementations including
`gdbserver <https://sourceware.org/gdb/current/onlinedocs/gdb.html/Server.html>`_,
`kgdb <https://www.kernel.org/doc/html/latest/dev-tools/kgdb.html>`_,
`OpenOCD <https://openocd.org/>`_
and the
`QEMU gdbstub <https://qemu-project.gitlab.io/qemu/system/gdb.html>`_.

Currently the gdbremote support in drgn is absolutely minimal:

* drgn can only connect to network sockets (use socat to bridge to stubs
that are not networked)
* only a single thread is supported
* there is no support for automatically handle address space layout
randomization (ASLR)
* register packet decoding is implemented only for AArch64

However, even this minimal support is sufficient to connect to the gdbserver,
read memory and generate a stack trace using AArch64 frame pointers::

sh$ drgn --gdbremote localhost:2345 --symbols ./hello
drgn 0.0.27+67.ge8a745c3 (using Python 3.11.2, elfutils 0.188, without libkdumpfile)
For help, type help(drgn).
>>> import drgn
>>> from drgn import FaultError, NULL, Object, cast, container_of, execscript, offsetof, reinterpret, sizeof, stack_trace
>>> from drgn.helpers.common import *
>>> prog['main']
(int (int argc, const char **argv))0x754
>>> prog.threads()
<_drgn._ThreadIterator object at 0x7f81b570d0>
>>> prog.main_thread().stack_trace()
#0 0x5555550764 <--- Symbol lookup currently fails due to ASLR offsets
#1 0x7ff7e17740
#2 0x7ff7e17818
>>> prog.main_thread().stack_trace()[0].registers()['x0']
1
>>> argv = prog.main_thread().stack_trace()[0].registers()['x1']
>>> argv0 = prog.read_u64(argv)
>>> prog.read(argv0, 8)
b'./hello\x00'
>>>
12 changes: 12 additions & 0 deletions drgn/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ def _main() -> None:
program_group.add_argument(
"-c", "--core", metavar="PATH", type=str, help="debug the given core dump"
)
program_group.add_argument(
"--gdbremote",
metavar="CONN",
type=str,
help="connect to the specified gdbserver",
)
program_group.add_argument(
"-p",
"--pid",
Expand Down Expand Up @@ -292,6 +298,12 @@ def _main() -> None:
sys.exit(
f"{e}\nerror: attaching to live process requires ptrace attach permissions"
)
elif args.gdbremote is not None:
prog.set_gdbremote(args.gdbremote)

# Suppress default symbol loading (at present, gdbremote always
# needs to get symbols from --symbols)
args.default_symbols = {}
else:
try:
prog.set_kernel()
Expand Down
2 changes: 2 additions & 0 deletions libdrgn/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ libdrgnimpl_la_SOURCES = $(ARCH_DEFS_PYS:_defs.py=.c) \
hash_table.c \
hash_table.h \
helpers.h \
gdbremote.c \
gdbremote.h \
io.c \
io.h \
language.c \
Expand Down
10 changes: 10 additions & 0 deletions libdrgn/arch_aarch64.c
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,15 @@ linux_kernel_get_initial_registers_aarch64(const struct drgn_object *task_obj,
return NULL;
}

static struct drgn_error *
gdbremote_get_initial_registers_aarch64(struct drgn_program *prog,
const void *regs, size_t reglen,
struct drgn_register_state **ret)
{
return get_initial_registers_from_struct_aarch64(prog, regs, reglen,
ret);
}

static struct drgn_error *
apply_elf_reloc_aarch64(const struct drgn_relocating_section *relocating,
uint64_t r_offset, uint32_t r_type, const int64_t *r_addend,
Expand Down Expand Up @@ -473,6 +482,7 @@ const struct drgn_architecture_info arch_info_aarch64 = {
.prstatus_get_initial_registers = prstatus_get_initial_registers_aarch64,
.linux_kernel_get_initial_registers =
linux_kernel_get_initial_registers_aarch64,
.gdbremote_get_initial_registers = gdbremote_get_initial_registers_aarch64,
.apply_elf_reloc = apply_elf_reloc_aarch64,
.linux_kernel_pgtable_iterator_create =
linux_kernel_pgtable_iterator_create_aarch64,
Expand Down
11 changes: 11 additions & 0 deletions libdrgn/drgn.h
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,8 @@ enum drgn_program_flags {
DRGN_PROGRAM_IS_LIVE = (1 << 1),
/** The program is running on the local machine. */
DRGN_PROGRAM_IS_LOCAL = (1 << 2),
/** The program is connected via the gdbremote protocol. */
DRGN_PROGRAM_IS_GDBREMOTE = (1 << 3),
};

/**
Expand Down Expand Up @@ -802,6 +804,15 @@ struct drgn_error *drgn_program_set_core_dump(struct drgn_program *prog,
*/
struct drgn_error *drgn_program_set_core_dump_fd(struct drgn_program *prog, int fd);

/**
* Set a @ref drgn_program to a gdbremote server.
*
* @param[in] conn gdb connection string (e.g. localhost:2345)
* @return @c NULL on success, non-@c NULL on error.
*/
struct drgn_error *drgn_program_set_gdbremote(struct drgn_program *prog,
const char *conn);

/**
* Set a @ref drgn_program to the running operating system kernel.
*
Expand Down
Loading

0 comments on commit b81f461

Please sign in to comment.