|
@@ -1202,6 +1202,372 @@ static off_t kcore__write(struct kcore *kcore)
|
|
|
return elf_update(kcore->elf, ELF_C_WRITE);
|
|
|
}
|
|
|
|
|
|
+struct phdr_data {
|
|
|
+ off_t offset;
|
|
|
+ u64 addr;
|
|
|
+ u64 len;
|
|
|
+};
|
|
|
+
|
|
|
+struct kcore_copy_info {
|
|
|
+ u64 stext;
|
|
|
+ u64 etext;
|
|
|
+ u64 first_symbol;
|
|
|
+ u64 last_symbol;
|
|
|
+ u64 first_module;
|
|
|
+ u64 last_module_symbol;
|
|
|
+ struct phdr_data kernel_map;
|
|
|
+ struct phdr_data modules_map;
|
|
|
+};
|
|
|
+
|
|
|
+static int kcore_copy__process_kallsyms(void *arg, const char *name, char type,
|
|
|
+ u64 start)
|
|
|
+{
|
|
|
+ struct kcore_copy_info *kci = arg;
|
|
|
+
|
|
|
+ if (!symbol_type__is_a(type, MAP__FUNCTION))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (strchr(name, '[')) {
|
|
|
+ if (start > kci->last_module_symbol)
|
|
|
+ kci->last_module_symbol = start;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!kci->first_symbol || start < kci->first_symbol)
|
|
|
+ kci->first_symbol = start;
|
|
|
+
|
|
|
+ if (!kci->last_symbol || start > kci->last_symbol)
|
|
|
+ kci->last_symbol = start;
|
|
|
+
|
|
|
+ if (!strcmp(name, "_stext")) {
|
|
|
+ kci->stext = start;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!strcmp(name, "_etext")) {
|
|
|
+ kci->etext = start;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__parse_kallsyms(struct kcore_copy_info *kci,
|
|
|
+ const char *dir)
|
|
|
+{
|
|
|
+ char kallsyms_filename[PATH_MAX];
|
|
|
+
|
|
|
+ scnprintf(kallsyms_filename, PATH_MAX, "%s/kallsyms", dir);
|
|
|
+
|
|
|
+ if (symbol__restricted_filename(kallsyms_filename, "/proc/kallsyms"))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ if (kallsyms__parse(kallsyms_filename, kci,
|
|
|
+ kcore_copy__process_kallsyms) < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__process_modules(void *arg,
|
|
|
+ const char *name __maybe_unused,
|
|
|
+ u64 start)
|
|
|
+{
|
|
|
+ struct kcore_copy_info *kci = arg;
|
|
|
+
|
|
|
+ if (!kci->first_module || start < kci->first_module)
|
|
|
+ kci->first_module = start;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__parse_modules(struct kcore_copy_info *kci,
|
|
|
+ const char *dir)
|
|
|
+{
|
|
|
+ char modules_filename[PATH_MAX];
|
|
|
+
|
|
|
+ scnprintf(modules_filename, PATH_MAX, "%s/modules", dir);
|
|
|
+
|
|
|
+ if (symbol__restricted_filename(modules_filename, "/proc/modules"))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ if (modules__parse(modules_filename, kci,
|
|
|
+ kcore_copy__process_modules) < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void kcore_copy__map(struct phdr_data *p, u64 start, u64 end, u64 pgoff,
|
|
|
+ u64 s, u64 e)
|
|
|
+{
|
|
|
+ if (p->addr || s < start || s >= end)
|
|
|
+ return;
|
|
|
+
|
|
|
+ p->addr = s;
|
|
|
+ p->offset = (s - start) + pgoff;
|
|
|
+ p->len = e < end ? e - s : end - s;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__read_map(u64 start, u64 len, u64 pgoff, void *data)
|
|
|
+{
|
|
|
+ struct kcore_copy_info *kci = data;
|
|
|
+ u64 end = start + len;
|
|
|
+
|
|
|
+ kcore_copy__map(&kci->kernel_map, start, end, pgoff, kci->stext,
|
|
|
+ kci->etext);
|
|
|
+
|
|
|
+ kcore_copy__map(&kci->modules_map, start, end, pgoff, kci->first_module,
|
|
|
+ kci->last_module_symbol);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__read_maps(struct kcore_copy_info *kci, Elf *elf)
|
|
|
+{
|
|
|
+ if (elf_read_maps(elf, true, kcore_copy__read_map, kci) < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__calc_maps(struct kcore_copy_info *kci, const char *dir,
|
|
|
+ Elf *elf)
|
|
|
+{
|
|
|
+ if (kcore_copy__parse_kallsyms(kci, dir))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ if (kcore_copy__parse_modules(kci, dir))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ if (kci->stext)
|
|
|
+ kci->stext = round_down(kci->stext, page_size);
|
|
|
+ else
|
|
|
+ kci->stext = round_down(kci->first_symbol, page_size);
|
|
|
+
|
|
|
+ if (kci->etext) {
|
|
|
+ kci->etext = round_up(kci->etext, page_size);
|
|
|
+ } else if (kci->last_symbol) {
|
|
|
+ kci->etext = round_up(kci->last_symbol, page_size);
|
|
|
+ kci->etext += page_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ kci->first_module = round_down(kci->first_module, page_size);
|
|
|
+
|
|
|
+ if (kci->last_module_symbol) {
|
|
|
+ kci->last_module_symbol = round_up(kci->last_module_symbol,
|
|
|
+ page_size);
|
|
|
+ kci->last_module_symbol += page_size;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!kci->stext || !kci->etext)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ if (kci->first_module && !kci->last_module_symbol)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ return kcore_copy__read_maps(kci, elf);
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__copy_file(const char *from_dir, const char *to_dir,
|
|
|
+ const char *name)
|
|
|
+{
|
|
|
+ char from_filename[PATH_MAX];
|
|
|
+ char to_filename[PATH_MAX];
|
|
|
+
|
|
|
+ scnprintf(from_filename, PATH_MAX, "%s/%s", from_dir, name);
|
|
|
+ scnprintf(to_filename, PATH_MAX, "%s/%s", to_dir, name);
|
|
|
+
|
|
|
+ return copyfile_mode(from_filename, to_filename, 0400);
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__unlink(const char *dir, const char *name)
|
|
|
+{
|
|
|
+ char filename[PATH_MAX];
|
|
|
+
|
|
|
+ scnprintf(filename, PATH_MAX, "%s/%s", dir, name);
|
|
|
+
|
|
|
+ return unlink(filename);
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__compare_fds(int from, int to)
|
|
|
+{
|
|
|
+ char *buf_from;
|
|
|
+ char *buf_to;
|
|
|
+ ssize_t ret;
|
|
|
+ size_t len;
|
|
|
+ int err = -1;
|
|
|
+
|
|
|
+ buf_from = malloc(page_size);
|
|
|
+ buf_to = malloc(page_size);
|
|
|
+ if (!buf_from || !buf_to)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ while (1) {
|
|
|
+ /* Use read because mmap won't work on proc files */
|
|
|
+ ret = read(from, buf_from, page_size);
|
|
|
+ if (ret < 0)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ if (!ret)
|
|
|
+ break;
|
|
|
+
|
|
|
+ len = ret;
|
|
|
+
|
|
|
+ if (readn(to, buf_to, len) != (int)len)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ if (memcmp(buf_from, buf_to, len))
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ err = 0;
|
|
|
+out:
|
|
|
+ free(buf_to);
|
|
|
+ free(buf_from);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__compare_files(const char *from_filename,
|
|
|
+ const char *to_filename)
|
|
|
+{
|
|
|
+ int from, to, err = -1;
|
|
|
+
|
|
|
+ from = open(from_filename, O_RDONLY);
|
|
|
+ if (from < 0)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ to = open(to_filename, O_RDONLY);
|
|
|
+ if (to < 0)
|
|
|
+ goto out_close_from;
|
|
|
+
|
|
|
+ err = kcore_copy__compare_fds(from, to);
|
|
|
+
|
|
|
+ close(to);
|
|
|
+out_close_from:
|
|
|
+ close(from);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static int kcore_copy__compare_file(const char *from_dir, const char *to_dir,
|
|
|
+ const char *name)
|
|
|
+{
|
|
|
+ char from_filename[PATH_MAX];
|
|
|
+ char to_filename[PATH_MAX];
|
|
|
+
|
|
|
+ scnprintf(from_filename, PATH_MAX, "%s/%s", from_dir, name);
|
|
|
+ scnprintf(to_filename, PATH_MAX, "%s/%s", to_dir, name);
|
|
|
+
|
|
|
+ return kcore_copy__compare_files(from_filename, to_filename);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * kcore_copy - copy kallsyms, modules and kcore from one directory to another.
|
|
|
+ * @from_dir: from directory
|
|
|
+ * @to_dir: to directory
|
|
|
+ *
|
|
|
+ * This function copies kallsyms, modules and kcore files from one directory to
|
|
|
+ * another. kallsyms and modules are copied entirely. Only code segments are
|
|
|
+ * copied from kcore. It is assumed that two segments suffice: one for the
|
|
|
+ * kernel proper and one for all the modules. The code segments are determined
|
|
|
+ * from kallsyms and modules files. The kernel map starts at _stext or the
|
|
|
+ * lowest function symbol, and ends at _etext or the highest function symbol.
|
|
|
+ * The module map starts at the lowest module address and ends at the highest
|
|
|
+ * module symbol. Start addresses are rounded down to the nearest page. End
|
|
|
+ * addresses are rounded up to the nearest page. An extra page is added to the
|
|
|
+ * highest kernel symbol and highest module symbol to, hopefully, encompass that
|
|
|
+ * symbol too. Because it contains only code sections, the resulting kcore is
|
|
|
+ * unusual. One significant peculiarity is that the mapping (start -> pgoff)
|
|
|
+ * is not the same for the kernel map and the modules map. That happens because
|
|
|
+ * the data is copied adjacently whereas the original kcore has gaps. Finally,
|
|
|
+ * kallsyms and modules files are compared with their copies to check that
|
|
|
+ * modules have not been loaded or unloaded while the copies were taking place.
|
|
|
+ *
|
|
|
+ * Return: %0 on success, %-1 on failure.
|
|
|
+ */
|
|
|
+int kcore_copy(const char *from_dir, const char *to_dir)
|
|
|
+{
|
|
|
+ struct kcore kcore;
|
|
|
+ struct kcore extract;
|
|
|
+ size_t count = 2;
|
|
|
+ int idx = 0, err = -1;
|
|
|
+ off_t offset = page_size, sz, modules_offset = 0;
|
|
|
+ struct kcore_copy_info kci = { .stext = 0, };
|
|
|
+ char kcore_filename[PATH_MAX];
|
|
|
+ char extract_filename[PATH_MAX];
|
|
|
+
|
|
|
+ if (kcore_copy__copy_file(from_dir, to_dir, "kallsyms"))
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ if (kcore_copy__copy_file(from_dir, to_dir, "modules"))
|
|
|
+ goto out_unlink_kallsyms;
|
|
|
+
|
|
|
+ scnprintf(kcore_filename, PATH_MAX, "%s/kcore", from_dir);
|
|
|
+ scnprintf(extract_filename, PATH_MAX, "%s/kcore", to_dir);
|
|
|
+
|
|
|
+ if (kcore__open(&kcore, kcore_filename))
|
|
|
+ goto out_unlink_modules;
|
|
|
+
|
|
|
+ if (kcore_copy__calc_maps(&kci, from_dir, kcore.elf))
|
|
|
+ goto out_kcore_close;
|
|
|
+
|
|
|
+ if (kcore__init(&extract, extract_filename, kcore.elfclass, false))
|
|
|
+ goto out_kcore_close;
|
|
|
+
|
|
|
+ if (!kci.modules_map.addr)
|
|
|
+ count -= 1;
|
|
|
+
|
|
|
+ if (kcore__copy_hdr(&kcore, &extract, count))
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ if (kcore__add_phdr(&extract, idx++, offset, kci.kernel_map.addr,
|
|
|
+ kci.kernel_map.len))
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ if (kci.modules_map.addr) {
|
|
|
+ modules_offset = offset + kci.kernel_map.len;
|
|
|
+ if (kcore__add_phdr(&extract, idx, modules_offset,
|
|
|
+ kci.modules_map.addr, kci.modules_map.len))
|
|
|
+ goto out_extract_close;
|
|
|
+ }
|
|
|
+
|
|
|
+ sz = kcore__write(&extract);
|
|
|
+ if (sz < 0 || sz > offset)
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ if (copy_bytes(kcore.fd, kci.kernel_map.offset, extract.fd, offset,
|
|
|
+ kci.kernel_map.len))
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ if (modules_offset && copy_bytes(kcore.fd, kci.modules_map.offset,
|
|
|
+ extract.fd, modules_offset,
|
|
|
+ kci.modules_map.len))
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ if (kcore_copy__compare_file(from_dir, to_dir, "modules"))
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ if (kcore_copy__compare_file(from_dir, to_dir, "kallsyms"))
|
|
|
+ goto out_extract_close;
|
|
|
+
|
|
|
+ err = 0;
|
|
|
+
|
|
|
+out_extract_close:
|
|
|
+ kcore__close(&extract);
|
|
|
+ if (err)
|
|
|
+ unlink(extract_filename);
|
|
|
+out_kcore_close:
|
|
|
+ kcore__close(&kcore);
|
|
|
+out_unlink_modules:
|
|
|
+ if (err)
|
|
|
+ kcore_copy__unlink(to_dir, "modules");
|
|
|
+out_unlink_kallsyms:
|
|
|
+ if (err)
|
|
|
+ kcore_copy__unlink(to_dir, "kallsyms");
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
int kcore_extract__create(struct kcore_extract *kce)
|
|
|
{
|
|
|
struct kcore kcore;
|