drgn/libdrgn/handler.c
Omar Sandoval 7bb3f0cd5a libdrgn: add interface for registering chains of named handlers
This will be used to allow providing names for type, object, and symbol
finders and configuring which ones are called and in what order. We
might even want this for memory readers. I'm assuming there will only be
a handful of handlers on a given list, but the enabled handlers will be
called frequently, and there may be many lists. The implementation is
therefore optimized for fast iteration and small size, and we don't
cache anything that would speed up reconfiguring the list.

Signed-off-by: Omar Sandoval <osandov@osandov.com>
2024-06-05 13:40:26 -07:00

139 lines
4.0 KiB
C

// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <stdlib.h>
#include <string.h>
#include "cleanup.h"
#include "drgn.h"
#include "handler.h"
#include "hash_table.h"
#include "util.h"
struct drgn_error *drgn_handler_list_register(struct drgn_handler_list *list,
struct drgn_handler *new_handler,
size_t enable_idx,
const char *what)
{
struct drgn_handler **insert_pos = &list->head;
size_t num_enabled = 0;
for (struct drgn_handler *handler = list->head; handler;
handler = handler->next) {
if (strcmp(new_handler->name, handler->name) == 0) {
return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT,
"duplicate %s name '%s'",
what, handler->name);
}
if (handler->enabled && num_enabled < enable_idx) {
insert_pos = &handler->next;
num_enabled++;
}
}
new_handler->enabled = enable_idx != DRGN_HANDLER_REGISTER_DONT_ENABLE;
new_handler->next = *insert_pos;
*insert_pos = new_handler;
return NULL;
}
#define drgn_handler_list_for_each_registered(handler, list) \
for (struct drgn_handler *handler = (list)->head; handler; \
handler = handler->next)
struct drgn_error *drgn_handler_list_registered(struct drgn_handler_list *list,
const char ***names_ret,
size_t *count_ret)
{
size_t n = 0;
drgn_handler_list_for_each_registered(handler, list)
n++;
const char **names = malloc_array(n, sizeof(names[0]));
if (!names)
return &drgn_enomem;
size_t i = 0;
drgn_handler_list_for_each_registered(handler, list)
names[i++] = handler->name;
*names_ret = names;
*count_ret = n;
return NULL;
}
static inline const char *drgn_handler_entry_to_key(const uintptr_t *entry)
{
return ((struct drgn_handler *)(*entry & ~1))->name;
}
DEFINE_HASH_TABLE(drgn_handler_table, uintptr_t, drgn_handler_entry_to_key,
c_string_key_hash_pair, c_string_key_eq);
struct drgn_error *drgn_handler_list_set_enabled(struct drgn_handler_list *list,
const char * const *names,
size_t count, const char *what)
{
// Put all of the handlers in a hash table of tagged pointers.
_cleanup_(drgn_handler_table_deinit)
struct drgn_handler_table table = HASH_TABLE_INIT;
drgn_handler_list_for_each_registered(handler, list) {
uintptr_t entry = (uintptr_t)handler;
if (drgn_handler_table_insert(&table, &entry, NULL) < 0)
return &drgn_enomem;
}
// Check the list of names.
for (size_t i = 0; i < count; i++) {
auto it = drgn_handler_table_search(&table, &names[i]);
if (!it.entry) {
return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT,
"%s '%s' not found", what,
names[i]);
}
if (*it.entry & 1) {
return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT,
"%s '%s' enabled multiple times",
what, names[i]);
}
*it.entry |= 1;
}
// Insert the enabled handlers and delete them from the hash table.
struct drgn_handler **handlerp = &list->head;
for (size_t i = 0; i < count; i++) {
auto it = drgn_handler_table_search(&table, &names[i]);
struct drgn_handler *handler =
(struct drgn_handler *)(*it.entry & ~1);
handler->enabled = true;
*handlerp = handler;
handlerp = &handler->next;
drgn_handler_table_delete_iterator(&table, it);
}
// The remaining handlers in the hash table are disabled. Insert them.
for (auto it = drgn_handler_table_first(&table); it.entry;
it = drgn_handler_table_next(it)) {
struct drgn_handler *handler = (struct drgn_handler *)*it.entry;
handler->enabled = false;
*handlerp = handler;
handlerp = &handler->next;
}
*handlerp = NULL;
return NULL;
}
struct drgn_error *drgn_handler_list_enabled(struct drgn_handler_list *list,
const char ***names_ret,
size_t *count_ret)
{
size_t n = 0;
drgn_handler_list_for_each_enabled(struct drgn_handler, handler, list)
n++;
const char **names = malloc_array(n, sizeof(names[0]));
if (!names)
return &drgn_enomem;
size_t i = 0;
drgn_handler_list_for_each_enabled(struct drgn_handler, handler, list)
names[i++] = handler->name;
*names_ret = names;
*count_ret = n;
return NULL;
}