Commit Graph

1325 Commits

Author SHA1 Message Date
Omar Sandoval
9397a11605 libdrgn: export drgn_language instances
libdrgn currently exports struct drgn_language pointers from
drgn_program_language(), drgn_type_language(), and
drgn_object_language(), but doesn't provide any way to do anything with
them. Export our drgn_language instances and add drgn_language_name() so
that they can at least be compared and printed.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-16 13:07:42 -08:00
Omar Sandoval
5d65ebb04b libdrgn: don't store language structures in one array
In the next change, we want to export languages to the public libdrgn
interface. I couldn't figure out any way to export array elements as
their own symbols. I'd also rather not export the drgn_languages array
indices as an enum because that would preclude ever having any sort of
language plugin support.

Instead, let's get rid of the drgn_languages array as it currently
exists and have separate drgn_language structures. This also allows us
to make a bunch of the C implementation functions static again. We keep
the language numbers so that we can store per-language data efficiently
(currently drgn_program::void_types and languages_py), as well as a
drgn_languages array to go from the language number to the struct
drgn_language. But, this is all internal and could be changed if we ever
support language plugins.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-16 12:47:12 -08:00
Omar Sandoval
16bb5d92e0 Add missing drgn.Language.CPP type annotation
We forgot to add this to _drgn.pyi back when we added the language
definition.

Fixes: d8fadf10ee ("libdrgn: Add cpp language and tests")
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-16 12:38:38 -08:00
Omar Sandoval
7f232a4815 pre-commit: update Black
Black 22.1.0 has some style changes: string prefixes are normalized and
spaces around the power operator are removed.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-12 13:48:49 -08:00
Omar Sandoval
12c2de2956 libdrgn: implement thread API for live processes
This implements the existing thread API methods for live processes other
than drgn_thread_stack_trace(). It also doesn't yet add support for
full-blown tracing, but it at least brings live processes to feature
parity. This is taken from the non-ptrace parts of Kevin Svetlitski's
PR #142, with some modifications.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-12 13:33:41 -08:00
Omar Sandoval
28c5a2016b libdrgn: split up some thread API functions
drgn_thread_iterator_create(), drgn_thread_iterator_next(), and
drgn_program_find_thread() have big, divergent code paths for different
targets, and this would get worse once we add live processes. Split them
up into multiple functions.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-12 01:42:37 -08:00
Omar Sandoval
c71300d024 libdrgn: use exact buffer sizes when formatting decimal numbers
We have a few places where we format a decimal number with sprintf() or
snprintf() to a buffer with an arbitrary size. Instead of this arbitrary
size, let's add a macro to get the exact number of characters required
to format a decimal number, use it in all of these places, and make all
of these places use snprintf() just to be safe. This is more verbose but
self-documenting. The max_decimal_length() macro is inspired by
https://stackoverflow.com/a/13546502/1811295 with some improvements.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-12 01:16:54 -08:00
Omar Sandoval
98577e5e23 libdrgn: fix drgn_program_find_thread() for Linux kernel when thread isn't found
If a TID does not exist, then linux_helper_find_task() succeeds but
returns a null pointer object. Check for that instead of returning a
bogus thread.

Fixes: 301cc767ba ("Implement a new API for representing threads")
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-12 01:16:49 -08:00
Mykola Lysenko
7580fffbdf Add drgn.Program.main_thread()
Currently only supported for user-space crash dumps. E.g. no support for
live user-space application debugging or kernel debugging.

Closes #144.

Signed-off-by: Mykola Lysenko <mykolal@fb.com>
2022-02-10 15:53:50 -08:00
Omar Sandoval
2a132caaee vmtest: elaborate on kernel builds in README
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-09 10:36:31 -08:00
Omar Sandoval
55e2bc063a libdrgn: python: fix TypeTemplateParameter argument leak
LeakSanitizer reported a leak of a Python object when running
tests.test_type.TestTypeTemplateParameter.test_callable. This one is
caused by a missing Py_DECREF() in an error case.

Fixes: 352c31e1ac ("Add support for C++ template parameters")
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-08 17:05:56 -08:00
Omar Sandoval
50ba745c24 libdrgn: python: fix drgn_thread_iterator leak
LeakSanitizer reported a leak of a drgn_thread_iterator when running the
unit tests. The root cause is that ThreadIterator_dealloc() isn't
freeing the underlying drgn_thread_iterator().

Fixes: 301cc767ba ("Implement a new API for representing threads")
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-08 17:05:36 -08:00
Omar Sandoval
914ad8c53d libdrgn: use memswitch for linux_kernel_object_find
Replace the hand-written if-else ladder of memcmp() calls with a
memswitch.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-08 02:03:11 -08:00
Omar Sandoval
248d16137b tests: check for PermissionError instead of EUID
When running under fakeroot (e.g., for an RPM mock build), geteuid()
returns 0, so LinuxHelperTestCase continues and fails with a
PermissionError either when attaching to /proc/kcore or opening
/dev/loop-control. Catch the PermissionError instead of checking the
EUID.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-08 00:56:08 -08:00
Omar Sandoval
88decf6c0e setup.py: add 5.17 to vmtest kernels
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-07 18:26:01 -08:00
Omar Sandoval
c49bba41b2 libdrgn: language_c: replace c_keywords with memswitch
GCC or binutils on Fedora Rawhide for ARM seems to have a bug where
c_keywords gets placed in the .data.rel.ro section (see
https://www.airs.com/blog/archives/189):

$ readelf -s .libs/libdrgnimpl_la-language_c.o | grep -w c_keywords
   475: 00000000    16 OBJECT  LOCAL  DEFAULT  175 c_keywords
$ readelf -S .libs/libdrgnimpl_la-language_c.o | grep -F '[175]'
  [175] .data.rel         PROGBITS        00000000 051f90 000010 00  WA  0   0  4
$ readelf -s .libs/_drgn.so | grep -w c_keywords
  9267: 0008e84c    16 OBJECT  LOCAL  DEFAULT   21 c_keywords.lto_priv.0
$ readelf -S .libs/_drgn.so | grep -F '[21]'
  [21] .data.rel.ro      PROGBITS        0008e018 07e018 000a10 00  WA  0   0  8

This results in a crash on startup when c_keywords_init() attempts to
populate c_keywords.

While this appears to be a compiler or linker bug, I've been meaning to
replace c_keywords with a static lookup function anyways. Now that we
have gen_strswitch.py, we can use it to generate the lookup function.
Add a script, gen_c_keywords_inc_strswitch.py, which generates an array
mapping token kind to spelling, and a memswitch mapping spelling to
token kind.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-04 20:26:35 -08:00
Omar Sandoval
da01da3a2d Add gen_strswitch.py
We have multiple places where we match an input string against several
cases:

* drgn_lexer_c() checks C identifiers against a runtime hash table of C
  keywords.
* linux_kernel_object_find() has an if-else ladder of checks for object
  names.
* drgn_debug_info_find_sections() loops over an array of ELF section
  names to look for sections we need.
* libdrgn/build-aux/gen_arch.awk generates a compile-time trie using
  nested switch statements to match register names.

This commit adds a script, gen_strswitch.py, that can hopefully be used
to replace all of these. gen_strswitch.py generalizes the compile-time
trie idea from gen_arch.awk in a few ways:

* It has syntax and semantics based on C switch statements.
* It supports both null-terminated strings and strings with an explicit
  length.
* It compresses unique substrings to calls to strcmp(), strncmp(), or
  memcmp() when appropriate.

In benchmarks, this approach is more performant than the above options
as well as a candidate based on gperf, while resulting in machine code
around the same size as the straightforward if-else ladder approach.

Future commits will convert the use cases above to use this script.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-04 20:26:35 -08:00
Omar Sandoval
41de5d72a2 Require Python to build libdrgn
Currently, Python is only required to build the Python bindings. I
originally wanted to avoid having Python as a build dependency of
libdrgn, which is why gen_arch is an AWK script. However, I want to add
another code generation script which is harder to do in AWK.
Additionally, these days more people are familiar with Python than AWK,
so let's just bite the bullet and require Python to build. No one builds
libdrgn by itself anyways.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-02-04 20:26:35 -08:00
Kevin Svetlitski
0b9f03752a Add autoconf option to enable ASAN
ASAN is incredibly useful during development, especially when dealing
with non-deterministic behavior where re-running the code under a debugger
won't necessarily reproduce the bug each time. In order not to break any
existing workflows, building with ASAN is opt-in (via --enable-asan).

Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-02-02 17:04:05 -08:00
Kevin Svetlitski
d51843017e Fix double-free of crashed_thread
Running the test suite with ASAN enabled revealed that when
the current target was a userspace core dump, the `crashed_thread`
member of `struct drgn_program` was being freed twice – once indirectly
via `drgn_thread_set_deinit`, and once explicitly in `drgn_prog_deinit`.

Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-02-02 16:51:21 -08:00
Omar Sandoval
e59c779652 libdrgn: link against libm
libdrgn uses rint() for formatting floating-point numbers. rint() is
provided by libm, so we need to link with -lm.

This missing library has been masked for a couple of reasons:

1. Python is linked against libm, so the drgn Python bindings implicitly
   have this dependency satisfied.
2. On x86-64, GCC has a builtin implementation of rint().

This can be reproduced on x86-64 by building examples/load_debug_info
with -fno-builtin.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-27 17:40:07 -08:00
Omar Sandoval
929b7de266 libdrgn: handle reading data from SHT_NOBITS sections
Peilin Ye reported a couple of related crashes in drgn caused by Linux
kernel modules which had been processed with objcopy --only-keep-debug
(although he notes that since binutils-gdb commit 8c803a2dd7d3
("elf_backend_section_flags and _bfd_elf_init_private_section_data") (in
binutils v2.35), objcopy --only-keep-debug doesn't seem to work for
kernel modules).

If given an SHT_NOBITS section, elf_getdata() returns an Elf_Data with
d_buf = NULL and d_size set to the size in the section header, which is
often non-zero. There are a few places where this can cause us to
dereference a NULL pointer:

* In relocate_elf_sections() for the relocated section data.
* In relocate_elf_sections() for the symbol table section data.
* In get_kernel_module_name_from_modinfo().
* In get_kernel_module_name_from_this_module().

Fix it by checking the section type or directly checking Elf_Data::d_buf
everywhere that could potentially get an SHT_NOBITS section. This is
based on a PR from Peilin Ye.

Closes #145.

Reported-by: Peilin Ye <peilin.ye@bytedance.com>
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-27 12:23:09 -08:00
Omar Sandoval
8e8e3a4f57 libdrgn: debug_info: refactor relocate_elf_section()
relocate_elf_section() shouldn't need to deal with reading the sections.
Pull that logic out into relocate_elf_file() (which will be shared with
REL-style relocations when we support those) and rename
relocate_elf_section() to apply_elf_relas().

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-27 12:22:57 -08:00
Omar Sandoval
26ff3667cb libdrgn: debug_info: use elf_rawdata() instead of elf_getdata()
Most of the places where we call elf_getdata() (via read_elf_section())
deal with SHT_PROGBITS sections. elf_getdata() always returns the
literal contents of the file for SHT_PROGBITS sections (usually straight
out of the mmap'd file).

The exceptions are the SHT_RELA and SHT_SYMTAB sections in
relocate_elf_section(). For these, if the byte order or alignment of the
file do not match the host, elf_getdata() allocates a new buffer and
converts the contents. relocate_elf_section() also handles unaligned
buffers and swaps the byte order, so it mistakenly ends up with the
original byte order of the file.

Rather than removing that from relocate_elf_section(), let's avoid the
extra allocation and use elf_rawdata(), which always returns the literal
contents of the file.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-27 12:20:10 -08:00
Omar Sandoval
92cfb06d9b docs: update required Sphinx version
We don't need anything new, just picking up the newest version.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-22 11:22:36 -08:00
Omar Sandoval
fb4d2088c2 docs: use extlink in Kyber case study
Sphinx 4.4.0 warns that kyber_stack_trace.rst could use an extlink for
the link to the Kyber source code instead of hard-coding the link to the
Linux repo.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-22 11:20:23 -08:00
Omar Sandoval
085e6f3078 tests: remove no-op setUp() method
Commit 7d7aa7bf7b ("libdrgn/python: remove Type == operator") removed
the substantial part of tests.TestCase.setUp() but didn't remove the
method.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-22 11:12:52 -08:00
Omar Sandoval
4da28ba0a1 helpers: only lookup type once for for_each_entry helpers
We have several for_each_entry helpers that take a type as a str or
drgn.Type and pass that directly to container_of(). If a str is given,
then this looks up the type by name for every entry. We can optimize
this and only look up the type once. In a benchmark derived from
examples/linux/fs_inodes.py, iterating over ~900k entries was reduced
from ~2 seconds to ~1.6 seconds.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-22 11:02:12 -08:00
Omar Sandoval
3f65552d95 tests: add tests for Linux kernel linked list helpers
We indirectly test the linked list helpers via other helpers that use
them, but let's add some dedicated tests. We test against two lists:

* "modules", which is never empty in vmtest because we load the loop
  module.
* "vmcore_list", which is always empty except in the kdump kernel.

Like the red-black tree tests, these tests are written generically so we
can use a different list if necessary.

There aren't any great candidates for hlists, so we'll have to make do
with indirectly testing them for now.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-22 10:49:25 -08:00
Omar Sandoval
5f76848b98 tests: add tests for Linux kernel red-black tree helpers
The test cases use the VMA tree and cross-reference it with
/proc/$pid/maps, but they're written so it could easily be swapped out
for another tree if necessary.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-21 17:15:58 -08:00
Omar Sandoval
289cdafa7f helpers: fix rb_find() when entry is not found
We're passing the entry type to NULL instead of the entry pointer type,
which results in errors like:

    File "/home/osandov/repos/drgn/drgn/helpers/linux/rbtree.py", line 210, in rb_find
      return NULL(root.prog_, type)
  TypeError: 'struct vm_area_struct' value must be dictionary or mapping

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-21 16:58:49 -08:00
Omar Sandoval
0a643b6fab python: allow Program.type() to accept a Type
Some helpers can accept either a str or a Type. If they want to always
work with a Type internally, they need to do something like:

  if isinstance(type, str):
      type = prog.type(type)

Instead, let's let Program.type() accept a Type and return the exact
same type, so those helpers can unconditionally do:

  type = prog.type(type)

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-21 16:52:36 -08:00
Omar Sandoval
7393808a7d cli: diagnose when someone tries to run a binary as a script
This is a common mistake:

  $ drgn core_dump
  Traceback (most recent call last):
    File "/usr/bin/drgn", line 33, in <module>
      sys.exit(load_entry_point('drgn==0.0.16', 'console_scripts', 'drgn')())
    File "/usr/lib/python3.10/site-packages/drgn/internal/cli.py", line 133, in main
      runpy.run_path(args.script[0], init_globals=init_globals, run_name="__main__")
    File "/usr/lib/python3.10/runpy.py", line 268, in run_path
      code, fname = _get_code_from_file(run_name, path_name)
    File "/usr/lib/python3.10/runpy.py", line 242, in _get_code_from_file
      code = compile(f.read(), fname, 'exec')
  ValueError: source code string cannot contain null bytes

The user intends to debug the core dump, but they've actually specified
the core dump as a Python script to run. The error message from the
runpy internals does not make that clear. So, let's catch this earlier
by doing a quick-and-dirty test of the file magic to see if it looks
like a core dump or other ELF file. If so, we exit with a more helpful
message:

  $ drgn core_dump
  error: core_dump is a core dump
  Did you mean "-c core_dump"?
  $ drgn /usr/bin/ls
  error: /usr/bin/ls is a binary, not a drgn script

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-21 14:51:17 -08:00
Omar Sandoval
c40543b15c tests: add test cases for generic flag decode helpers
Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-15 11:45:09 -08:00
Stephen Brennan
7970a60818 Add methods to return multiple matching symbols
Currently we can lookup symbols by name or address, but this will only
return one symbol, prioritizing the global symbols. However, symbols may
share the same name, and symbols may also overlap address ranges, so
it's possible for searches to return multiple results. Add functions
which can return a list of multiple matching symbols.

Signed-off-by: Stephen Brennan <stephen@brennan.io>
2022-01-15 11:44:33 -08:00
Stephen Brennan
fb99f6dbe6 ci: Use pre-commit to run linters
Now that pre-commit is added, replace the manual commands for mypy,
isort, and black with equivalent pre-commit commands. This allows us to
avoid duplicating linter arguments. It also allows us to pin the linters
used in CI by way of the .pre-commit-config.yaml file, ensuring
reproducible lint errors.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
2022-01-14 13:31:16 -08:00
Stephen Brennan
52b96aed88 Run pre-commit on all files
`pre-commit run --all-files` results in the following minor
updates, which appear to be caused by my own failure to run linters.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
2022-01-14 13:31:16 -08:00
Stephen Brennan
ae377984d4 Add pre-commit
During PRs, lint and mypy errors can show up in the CI tests, which is
useful, but can introduce unnecessary churn on the PR as small lint
fixes are pushed. This commit adds (optional) support for pre-commit, a
tool which can be configured to run as a git pre-commit hook, running
linters on all changed code to catch issues before you push your code.

Signed-off-by: Stephen Brennan <stephen.s.brennan@oracle.com>
2022-01-14 13:31:16 -08:00
Omar Sandoval
e2fc4ce2ac helpers: add a helper for decoding page flags
As well as a couple of generic helpers backing it.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-12 16:57:10 -08:00
Kevin Svetlitski
301cc767ba Implement a new API for representing threads
Previously, drgn had no way to represent a thread – retrieving a stack
trace (the only extant thread-specific operation) was achieved by
requiring the user to directly provide a tid.

This commit introduces the scaffolding for the design outlined in
issue #92, and implements the corresponding methods for userspace core
dumps, the live Linux kernel, and Linux kernel core dumps. Future work
will build on top of this commit to support live userspace processes.

Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-01-11 17:28:17 -08:00
Kevin Svetlitski
78139b6ba3 libdrgn: add Linux kernel task iterator
The thread API needs a way to iterate over all task_structs in the
kernel. Previously, we translated the existing for_each_task helper,
which supports iterating through specific PID namespaces by walking
through the PID radix tree or PID hashtable. However, we don't need
specific namespaces for the thread API, so we can instead use the much
simpler linked lists of thread groups and threads.

Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-01-11 17:28:17 -08:00
Omar Sandoval
95c4e2d748 Revert "Rewrite linux helper iterators in C"
This reverts commit 2b47583c73. After
Kevin had completed this, we realized that there is a simpler method for
iterating through tasks from libdrgn, which the next commit will
implement. Revert the translation, but keep the improved
tests.helpers.linux.test_pid.TestPid.test_for_each_task.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-11 17:28:17 -08:00
Kevin Svetlitski
32a968deb0 vmtest: only disable SMP for the capture kernel when not using KVM acceleration
Disabling SMP is necessary to work around a bug in QEMU's handling of
the capture kernel, but makes the tests run much slower. However, this
bug only appears to manifest when KVM acceleration is disabled, so the
testing harness has been modified to only disable SMP when this is true.

[Omar: use an environment variable instead of touching a file]
Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-01-11 17:28:17 -08:00
Kevin Svetlitski
d3c9e24115 tests: make all tests inherit from drgn's TestCase class
The majority of test cases already inherited from drgn's TestCase class.
The few outliers that inherited directly from unittest.TestCase have
been brought in line with the other tests.

Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-01-11 17:28:17 -08:00
Kevin Svetlitski
ac2cadabcd Add framework for testing in kdump
Now that the vmtest kernel supports kdump, add a script that can be used
to crash and enter the kdump environment on demand. Use that to crash
after running the normal test suite so that we can run tests against
/proc/vmcore. vmcore tests live in their own directory; presently the
only test is a simple sanity check that ensures we can can attach to
/proc/vmcore.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Kevin Svetlitski <svetlitski@fb.com>
2022-01-07 14:03:00 -08:00
Omar Sandoval
69c069b09f libdrgn: allow NULL argument to drgn_stack_trace_destroy()
This is one place where I broke the convention that I just documented.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-06 18:23:27 -08:00
Omar Sandoval
2ce41c22ae CONTRIBUTING: mention that _destroy functions should allow NULL
This is another undocumented convention.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2022-01-06 18:21:46 -08:00
Omar Sandoval
2a0b4c8848 vmtest: also add kexec_file_load() syscall config options
We can avoid the need for the kexec tool if we load the kdump kernel
ourselves, which is much easier with kexec_file_load(). Add the config
options to enable it.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2021-12-21 23:32:49 -08:00
Omar Sandoval
ba93fd5a71 vmtest: add kdump kernel config options
We would like to test drgn against kernel core dumps (e.g., for #129).
One option would be to include some vmcore files in the repository and
test against those. But those can be huge, and we'd need a lot of them
to test different kernel versions. Instead, we can run vmtest, enable
kdump, and trigger a crash. To do that, we first need to enable a few
kernel config options.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2021-12-21 17:39:32 -08:00
Omar Sandoval
2ff58a4d45 libdrgn: linux: make per_cpu_ptr() support !SMP kernels
Kernels built without multiprocessing support don't have
__per_cpu_offset; instead, per_cpu_ptr() is a no-op. Make the helper do
the same and update the test case to work on !SMP as well.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2021-12-21 16:51:15 -08:00