drgn/libdrgn/memory_file_reader.c
Omar Sandoval 75c3679147 Rewrite drgn core in C
The current mixed Python/C implementation works well, but it has a
couple of important limitations:

- It's too slow for some common use cases, like iterating over large
  data structures.
- It can't be reused in utilities written in other languages.

This replaces the internals with a new library written in C, libdrgn. It
includes Python bindings with mostly the same public interface as
before, with some important improvements:

- Types are now represented by a single Type class rather than the messy
  polymorphism in the Python implementation.
- Qualifiers are a bitmask instead of a set of strings.
- Bit fields are not considered a separate type.
- The lvalue/rvalue terminology is replaced with reference/value.
- Structure, union, and array values are better supported.
- Function objects are supported.
- Program distinguishes between lookups of variables, constants, and
  functions.

The C rewrite is about 6x as fast as the original Python when using the
Python bindings, and about 8x when using the C API directly.

Currently, the exposed API in C is fairly conservative. In the future,
the memory reader, type index, and object index APIs will probably be
exposed for more flexibility.
2019-04-02 14:12:07 -07:00

168 lines
4.3 KiB
C

// Copyright 2018-2019 - Omar Sandoval
// SPDX-License-Identifier: GPL-3.0+
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "internal.h"
#include "memory_reader.h"
static struct drgn_error *pread_all(int fd, void *buf, size_t count,
off_t offset)
{
char *p = buf;
while (count) {
ssize_t ret;
ret = pread(fd, p, count, offset);
if (ret == -1) {
if (errno == EINTR)
continue;
return drgn_error_create_os(errno, NULL, "pread");
} else if (ret == 0) {
return drgn_error_format(DRGN_ERROR_FAULT,
"short read from memory file");
}
p += ret;
count -= ret;
offset += ret;
}
return NULL;
}
static struct drgn_error *
drgn_memory_file_reader_read(struct drgn_memory_reader *reader, void *buf,
uint64_t address, size_t count, bool physical)
{
struct drgn_error *err;
struct drgn_memory_file_reader *freader;
char *p = buf;
freader = container_of(reader, struct drgn_memory_file_reader, reader);
while (count) {
struct drgn_memory_file_segment *segment;
uint64_t segment_address;
uint64_t segment_offset;
off_t read_offset;
size_t read_count, zero_count;
int i;
/*
* The most recently used segments are at the end of the list,
* so search backwards.
*/
for (i = freader->num_segments - 1; i >= 0; i--) {
segment = &freader->segments[i];
segment_address = (physical ? segment->phys_addr :
segment->virt_addr);
if (segment_address == UINT64_MAX)
continue;
if (segment_address <= address &&
address < segment_address + segment->mem_size)
break;
}
if (i < 0) {
return drgn_error_format(DRGN_ERROR_FAULT,
"could not find memory segment containing 0x%" PRIx64,
address);
}
/* Move the used segment to the end of the list. */
if ((size_t)i != freader->num_segments - 1) {
struct drgn_memory_file_segment tmp = *segment;
memmove(&freader->segments[i],
&freader->segments[i + 1],
(freader->num_segments - i - 1) *
sizeof(*freader->segments));
segment = &freader->segments[freader->num_segments - 1];
*segment = tmp;
}
segment_offset = address - segment_address;
if (segment_offset < segment->file_size)
read_count = min(segment->file_size - segment_offset,
(uint64_t)count);
else
read_count = 0;
if (segment_offset + read_count < segment->mem_size)
zero_count = min(segment->mem_size - segment_offset - read_count,
(uint64_t)(count - read_count));
else
zero_count = 0;
read_offset = segment->file_offset + segment_offset;
err = pread_all(freader->fd, p, read_count, read_offset);
if (err)
return err;
memset(p + read_count, 0, zero_count);
p += read_count + zero_count;
count -= read_count + zero_count;
address += read_count + zero_count;
}
return NULL;
}
static void drgn_memory_file_reader_destroy(struct drgn_memory_reader *reader)
{
struct drgn_memory_file_reader *freader;
freader = container_of(reader, struct drgn_memory_file_reader, reader);
free(freader->segments);
free(freader);
}
static const struct drgn_memory_reader_ops drgn_memory_file_reader_ops = {
.destroy = drgn_memory_file_reader_destroy,
.read = drgn_memory_file_reader_read,
};
struct drgn_error *
drgn_memory_file_reader_create(int fd, struct drgn_memory_file_reader **ret)
{
struct drgn_memory_file_reader *freader;
freader = malloc(sizeof(*freader));
if (!freader)
return &drgn_enomem;
freader->reader.ops = &drgn_memory_file_reader_ops;
freader->segments = NULL;
freader->num_segments = 0;
freader->capacity = 0;
freader->fd = fd;
*ret = freader;
return NULL;
}
struct drgn_error *
drgn_memory_file_reader_add_segment(struct drgn_memory_file_reader *freader,
const struct drgn_memory_file_segment *segment)
{
if (segment->file_offset > OFF_MAX) {
return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT,
"segment offset does not fit in off_t");
}
if (segment->file_size > OFF_MAX - segment->file_offset) {
return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT,
"segment end does not fit in off_t");
}
if (freader->num_segments >= freader->capacity) {
size_t new_capacity;
if (freader->capacity)
new_capacity = freader->capacity * 2;
else
new_capacity = 1;
if (!resize_array(&freader->segments, new_capacity))
return &drgn_enomem;
freader->capacity = new_capacity;
}
freader->segments[freader->num_segments++] = *segment;
return NULL;
}