diff --git a/Makefile b/Makefile index 1af3c55..029b852 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,11 @@ -all: - $(MAKE) -C kpatch-diff-gen +SUBDIRS = kpatch-kmod kpatch-files tools + +test: test.c + $(CC) -g -o $@ $^ -lelf + +.PHONY: clean +clean: + for dir in $(SUBDIRS); do \ + $(MAKE) -C $$dir $@; \ + done + diff --git a/kmod/Kbuild b/kmod/Kbuild deleted file mode 100644 index 3203071..0000000 --- a/kmod/Kbuild +++ /dev/null @@ -1,8 +0,0 @@ -obj-m += kpatch.o -obj-m += dummy.o -obj-m += kpatch-patch.o - -kpatch-objs += base.o trampoline.o -kpatch-patch-objs += kpatch-patch-foo.o -dummy-objs += kpatch-patch-hook.o - diff --git a/kmod/kpatch-patch-hook.c b/kmod/kpatch-patch-hook.c deleted file mode 100644 index 20a0fc7..0000000 --- a/kmod/kpatch-patch-hook.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include "kpatch.h" - -#include -#include - -extern char __kpatch_relas, __kpatch_relas_end, - __kpatch_patches, __kpatch_patches_end; - - - -static int __init patch_init(void) -{ - int ret; - - ret = kpatch_register(THIS_MODULE, &__kpatch_relas, &__kpatch_relas_end, - &__kpatch_patches, &__kpatch_patches_end); - - return ret; -} - -static void __exit patch_exit(void) -{ - - int ret; - - ret = kpatch_unregister(THIS_MODULE); -} - -module_init(patch_init); -module_exit(patch_exit); -MODULE_LICENSE("GPL"); diff --git a/kmod/kpatch.h b/kmod/kpatch.h deleted file mode 100644 index ecef188..0000000 --- a/kmod/kpatch.h +++ /dev/null @@ -1,31 +0,0 @@ -#include - -#define KPATCH_MAX_FUNCS 256 - -struct kpatch_func { - unsigned long old_func_addr; - unsigned long new_func_addr; - char *old_func_name; - unsigned long old_func_addr_end; - struct module *mod; -}; - -struct kpatch_rela { - unsigned long dest; /* TODO share struct header file with elfdiff */ - unsigned long src; - unsigned long type; -}; - -struct kpatch_patch { - unsigned long new; - unsigned long orig; /* TODO eventually add name of symbol so we can verify it with kallsyms */ - unsigned long orig_end; /* TODO: rename this struct to kpatch_func, embed it within original kpatch_func, and rename original kpatch_func to kpatch_func_reg? */ -}; - -void kpatch_trampoline(unsigned long ip, unsigned long parent_ip, - struct ftrace_ops *op, struct pt_regs *regs); -int kpatch_register(struct module *mod, void *kpatch_relas, - void *kpatch_relas_end, void *kpatch_patches, - void *kpatch_patches_end); -int kpatch_unregister(struct module *mod); -void ftrace_hacks(void); diff --git a/kmod/kpatch.lds b/kmod/kpatch.lds deleted file mode 100644 index d6d9df2..0000000 --- a/kmod/kpatch.lds +++ /dev/null @@ -1,4 +0,0 @@ -__kpatch_relas = ADDR(__kpatch_relas); -__kpatch_relas_end = ADDR(__kpatch_relas) + SIZEOF(__kpatch_relas); -__kpatch_patches = ADDR(__kpatch_patches); -__kpatch_patches_end = ADDR(__kpatch_patches) + SIZEOF(__kpatch_patches); diff --git a/kpatch-create b/kpatch-create deleted file mode 100755 index cf72bf6..0000000 --- a/kpatch-create +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/bash -# vim: tabstop=4 shiftwidth=4 expandtab -set -o nounset -set -o errexit -set -o pipefail - -# TODO: log output to a file instead of /dev/null - -CUR_DIR="$PWD" -SCRIPT="`basename $BASH_SOURCE`" -SCRIPT_DIR="`dirname $BASH_SOURCE`" -SCRIPT_DIR="`readlink -f \"$SCRIPT_DIR\"`" - -KMOD_DIR="$SCRIPT_DIR/kmod" -KPATCH_REG="$KMOD_DIR/kpatch-register.o" -KPATCH_LDS="$KMOD_DIR/kpatch.lds" - -KPATCH_GEN="$SCRIPT_DIR/kpatch-diff-gen/kpatch-diff-gen" -KPATCHGCC="$SCRIPT_DIR/kpatch-gcc" -JOBS=`grep -c processor /proc/cpuinfo` -MAKE_CMD="make -j$JOBS vmlinux" -export CROSS_COMPILE="$KPATCHGCC " -# TODO make in function - -PROGRESS_FILE="kpatch-in-progress" - -PATCHED= -PATCH= -KERNEL_DIR= -OUTPUT_DIR= -NOCLEAN= - -usage () -{ - echo "usage: $SCRIPT -p [PATCH] -k [KERNEL DIR] -o [OUTPUT DIR]" >&2 - exit 1 -} - - -scriptecho () -{ - echo "$SCRIPT: $*" -} - -cleanup_objs () -{ - find "$KERNEL_DIR" -name "*.kpatch_orig" -o -name "*.kpatch_gen" | while read file; do - rm -f $file - done -} - -cleanup () -{ - scriptecho "cleaning up..." - rm -f "$PROGRESS_FILE" - [ ! "$NOCLEAN" ] && cleanup_objs - if [ $PATCHED ]; then - cd "$KERNEL_DIR" - patch -p1 -R < "$PATCH" - fi - - rm -rf "$TMPDIR" -} - -die () -{ - echo "$SCRIPT: error: $*" >&2 - exit 1 -} - -while getopts "p:k:o:n" arg; do - case "$arg" in - p) PATCH="$OPTARG" ;; - k) KERNEL_DIR="$OPTARG" ;; - o) OUTPUT_DIR="$OPTARG" ;; - n) NOCLEAN=1 ;; - *) usage ;; - esac -done - -[ ! "$PATCH" ] || [ ! "$KERNEL_DIR" ] || [ ! "$OUTPUT_DIR" ] && usage - -[ ! -f "$PATCH" ] && die "$PATCH doesn't exist" -[ ! -d "$KERNEL_DIR" ] && die "$KERNEL_DIR doesn't exist" -mkdir -p "$OUTPUT_DIR" - -PATCH="`readlink -f \"$PATCH\"`" -KERNEL_DIR="`readlink -f \"$KERNEL_DIR\"`" -OUTPUT_DIR="`readlink -f \"$OUTPUT_DIR\"`" - -TMPDIR="`mktemp -d`" -trap cleanup exit - -scriptecho "compiling original kernel" -rm -f "$PROGRESS_FILE" -cleanup_objs -cd "$KERNEL_DIR" -$MAKE_CMD > /dev/null -cp vmlinux vmlinux.kpatch_orig - -scriptecho "patching kernel" -patch -p1 < "$PATCH" -PATCHED=1 - -scriptecho "compiling patched kernel" -touch "$PROGRESS_FILE" -$MAKE_CMD > /dev/null -rm -f "$PROGRESS_FILE" - -scriptecho "diffing binaries" -find . -type f -name '*.o.kpatch_orig' | while read file; do - origfile="${file#./}" - newfile="${origfile%.kpatch_orig}" - [ ! -f "$newfile" ] && die "can't find $newfile" - num="`readelf -s \"$file\" |awk '{print $4}' |grep -c FILE`" - [ "$num" = 0 ] && die "unsupported change in (assembly?) file $file" - [ "$num" -gt 1 ] && die "$newfile has too many FILE symbols" - scriptecho "object changed: $newfile" - "$KPATCH_GEN" "$origfile" "$newfile" -v vmlinux.kpatch_orig -o "$newfile.kpatch_gen" -done - -scriptecho "generating kpatch modules" -unset CROSS_COMPILE -cp -a "$KMOD_DIR" "$TMPDIR/kmod" - -make -C "$KERNEL_DIR" M="$TMPDIR/kmod" kpatch-patch-hook.o > /dev/null - -cd $TMPDIR/kmod -find "$KERNEL_DIR" -name "*.kpatch_gen" -exec ld -m elf_x86_64 -r -o kpatch-patch-foo.o kpatch-patch-hook.o kpatch.lds {} + - -make -C "$KERNEL_DIR" M="$TMPDIR/kmod" kpatch.ko > /dev/null -make -C "$KERNEL_DIR" M="$TMPDIR/kmod" kpatch-patch.ko > /dev/null - -cp kpatch.ko kpatch-patch.ko "$OUTPUT_DIR" - -scriptecho success! diff --git a/kpatch-diff-gen/Makefile b/kpatch-diff-gen/Makefile deleted file mode 100644 index 1bb5cbc..0000000 --- a/kpatch-diff-gen/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -CC=gcc -CFLAGS=-g -Wall -PROG=kpatch-diff-gen - -.PHONY: all -all: $(PROG) - - -$(PROG): $(PROG).c - $(CC) $(CFLAGS) -o $@ $^ -lelf -ludis86 - diff --git a/kpatch-diff-gen/kpatch-diff-gen.c b/kpatch-diff-gen/kpatch-diff-gen.c deleted file mode 100644 index 0b7edb3..0000000 --- a/kpatch-diff-gen/kpatch-diff-gen.c +++ /dev/null @@ -1,1611 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct section { - struct section *next; - Elf_Scn *sec; - GElf_Shdr sh; - Elf_Data *data; - char *name; - struct section *twin, *twino; - size_t index; - int diff; -}; - -struct symbol { - struct symbol *next; - GElf_Sym sym; - char *name; - struct symbol *twin, *twinv, *twino; - struct section *sec; - size_t index; - int type; - int bind; - int diff; -}; - -struct kpatch_rela { - unsigned long dest; /* TODO don't rely on this being the first */ - unsigned long src; - unsigned long type; -}; - -struct rela { - struct rela *next; - GElf_Rela rela; - struct rela *twin; - struct section *rela_sec, *dest_sec; - struct symbol *src_sym, *dest_sym; - /* TODO: get right signed and # of bits for all these vars */ - long dest_off, src_off; - const char *src_str; - unsigned int type; - struct kpatch_rela *kpatch_rela; -}; - -struct kpatch_patch { - unsigned long new; /* TODO don't rely on this being the first */ - unsigned long orig; /* TODO eventually add name of symbol so we can verify it with kallsyms */ - unsigned long orig_end; -}; - - -struct arguments { - char *args[2]; - char *vmlinux; - char *outfile; -}; - -#define SYM_ADDED 1 -#define SYM_REMOVED 2 -#define SYM_CHANGED 3 -#define SEC_CHANGED 4 /* TODO: maybe not needed. although a sanity - check somewhere re: section size = sum of - its symbol sizes might be good? yes.*/ - -struct arguments args; -Elf *elf1, *elf2, *elfv, *elfo; -struct symbol *syms1, *syms2, *symsv, *symso; -struct section *secs1, *secs2, *secsv, *secso; -struct rela *relas1, *relas2; -int risky; - - - -#define ERROR(format, ...) \ - error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) - -#define elfname(elf) \ -({ \ - const char *name = NULL; \ - if (elf == elf1) \ - name = args.args[0]; \ - else if (elf == elf2) \ - name = args.args[1]; \ - else if (elf == elfv) \ - name = args.vmlinux; \ - else if (elf == elfo) \ - name = args.outfile; \ - name; \ -}) - - -#define ELF_ERROR(elf, str) \ - error(1, 0, "%s:%d: " str " failed for '%s': %s", __FUNCTION__, __LINE__, elfname(elf), elf_errmsg(-1)) - -#define RISKY(format, ...) \ -({ \ - printf("WARNING: " format "\n", ##__VA_ARGS__); \ - risky = 1; \ -}) - -#define DIFF_FATAL(format, ...) \ -({ \ - printf("%s:%d: " format "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - error(2, 0, "unreconcilable difference"); \ -}) - -#define DIFF(d, s) \ -({ \ - /*printf("%s:%d: found diff for %s\n", __FUNCTION__, __LINE__, s->name);*/ \ - s->diff = d; \ - if (s->twin) \ - s->twin->diff = d; \ -}) - -#define list_add(head, new) \ -({ \ - typeof(new) p = head; \ - if (!head) \ - head = new; \ - else { \ - while (p->next) \ - p = p->next; \ - p->next = new; \ - } \ -}) - -#define list_size(head) \ -({ \ - typeof(head) p; \ - int size = 0; \ - for (p = head; p; p = p->next) \ - size++; \ - size; \ -}) - -error_t parse_opt(int key, char *arg, struct argp_state *state) -{ - struct arguments *arguments = state->input; - - switch (key) { - case 'v': - arguments->vmlinux = arg; - break; - case 'o': - arguments->outfile = arg; - break; - case ARGP_KEY_ARG: - if (state->arg_num > 1) - argp_usage(state); - arguments->args[state->arg_num] = arg; - break; - case ARGP_KEY_END: - if (!arguments->args[0] || !arguments->args[1] || - !arguments->vmlinux || !arguments->outfile) - argp_usage(state); - break; - default: - return ARGP_ERR_UNKNOWN; - } - - return 0; -} -static const struct argp_option options[] = { - {NULL, 'v', "file", 0, "original vmlinux"}, - {NULL, 'o', "file", 0, "output file"}, - {}, -}; -static struct argp argp = { - .options = options, - .parser = parse_opt, - .args_doc = "FILE1.o FILE2.o", - .doc = "Compare two kernel .o files and generate an object containing the changed and/or new functions.", -}; - -static Elf *elf_open(const char *name, int *fd) -{ - Elf *elf; - - *fd = open(name, O_RDONLY); - if (*fd == -1) - error(1, errno, "open of '%s' failed", name); - - elf = elf_begin(*fd, ELF_C_READ_MMAP, NULL); - if (!elf) - error(1, 0, "elf_begin failed for '%s': %s", name, - elf_errmsg(-1)); - - return elf; -} - -struct section *find_section_by_index(struct section *secs, unsigned int index) -{ - struct section *sec; - - for (sec = secs; sec && sec->index != index; sec = sec->next) - ; - - return sec; -} - -struct section *find_section_by_name(struct section *secs, const char *name) -{ - struct section *sec; - - for (sec = secs; sec && strcmp(sec->name, name); sec = sec->next) - ; - - return sec; -} - -struct symbol *find_symbol_by_offset(struct symbol *syms, struct section *sec, - int off, long *sym_off) -{ - struct symbol *sym; - - for (sym = syms; sym; sym = sym->next) - if (sym->sec == sec && off >= sym->sym.st_value && - off < sym->sym.st_value + sym->sym.st_size) { - *sym_off = off - sym->sym.st_value; - return sym; - } - - return NULL; -} - -struct symbol *find_symbol_by_index(struct symbol *syms, size_t index) -{ - struct symbol *sym; - - for (sym = syms; sym && sym->index != index; sym = sym->next) - ; - - return sym; -} - -struct symbol *find_symbol_by_name(struct symbol *syms, const char *name) -{ - struct symbol *sym; - - for (sym = syms; sym && strcmp(sym->name, name); sym = sym->next) - ; - - return sym; -} - -int addend_offset(struct rela *rela) -{ - ud_t ud; - int rc; - - if (rela->type != R_X86_64_PC32) - return 0; - - ud_init(&ud); - ud_set_input_buffer(&ud, rela->dest_sec->data->d_buf + rela->dest_sym->sym.st_value, rela->dest_sym->sym.st_size); - ud_set_mode(&ud, 64); - - while (1) { - rc = ud_disassemble(&ud); - if (!rc) - ERROR("FIXME"); - - /* TODO: check if we're past the end of the function and error */ - - if (rela->dest_off >= ud_insn_off(&ud) && - rela->dest_off < ud_insn_off(&ud) + ud_insn_len(&ud)) - break; - } - - return ud_insn_off(&ud) + ud_insn_len(&ud) - rela->dest_off; -} - -void init_section_list(Elf *elf, struct section **secs) -{ - Elf_Scn *scn; - struct section *sec; - size_t shstrndx; - - if (elf_getshdrstrndx(elf, &shstrndx)) - ELF_ERROR(elf, "elf_getshdrstrndx"); - - scn = NULL; - while ((scn = elf_nextscn(elf, scn))) { - - sec = malloc(sizeof(*sec)); - memset(sec, 0, sizeof(*sec)); - sec->sec = scn; - - if (!gelf_getshdr(scn, &sec->sh)) - ELF_ERROR(elf, "gelf_getshdr"); - - sec->name = elf_strptr(elf, shstrndx, sec->sh.sh_name); - if (!sec->name) - ELF_ERROR(elf, "elf_strptr"); - - sec->data = NULL; - sec->data = elf_getdata(sec->sec, sec->data); - if (!sec->data) - ELF_ERROR(elf, "elf_getdata"); - /* TODO: check for any remaining data? */ - - sec->index = elf_ndxscn(sec->sec); - - list_add(*secs, sec); - } -} - - -void init_symbol_list(Elf *elf, struct section *secs, - struct symbol **syms) -{ - struct section *sec; - struct symbol *sym = NULL, *last_sym = NULL; - int count, i; - - sec = find_section_by_name(secs, ".symtab"); - if (!sec) - ERROR("missing symbol table"); - - count = sec->sh.sh_size / sec->sh.sh_entsize; - - for (i = 1; i < count; i++) { /* skip symbol 0 */ - - last_sym = sym; - sym = malloc(sizeof(*sym)); - memset(sym, 0, sizeof(*sym)); - - sym->index = i; - - if (!gelf_getsym(sec->data, i, &sym->sym)) - ELF_ERROR(elf, "gelf_getsym"); - - sym->name = elf_strptr(elf, sec->sh.sh_link, - sym->sym.st_name); - if (!sym->name) - ELF_ERROR(elf, "elf_strptr"); - - sym->type = GELF_ST_TYPE(sym->sym.st_info); - sym->bind = GELF_ST_BIND(sym->sym.st_info); - switch (sym->type) { - case STT_NOTYPE: /* TODO: compare ABS symbols */ - case STT_OBJECT: - case STT_FUNC: - case STT_SECTION: - case STT_FILE: /* TODO: FILE getting compared properly? */ - break; - default: - ERROR("%s: unknown symbol type %d", sym->name, - sym->type); - } - - if (sym->sym.st_shndx >= SHN_LORESERVE && - sym->sym.st_shndx <= SHN_HIRESERVE && - sym->sym.st_shndx != SHN_ABS) - ERROR("%s: I don't know how to handle reserved section " - "index %d for symbol %s", elfname(elf), - sym->sym.st_shndx, sym->name); - - if (sym->sym.st_shndx != SHN_UNDEF) - sym->sec = find_section_by_index(secs, - sym->sym.st_shndx); - else - sym->sec = NULL; - - if (sym->type == STT_SECTION) - sym->name = sym->sec->name; - - /* optimized list_add */ - if (!*syms) - *syms = sym; - else - last_sym->next = sym; - - } -} - - - -void init_rela_list(Elf *elf, struct section *secs, struct symbol *syms, - struct rela **relas) -{ - struct section *rela_sec, *dest_sec; - int count, i; - unsigned int off, index; - struct rela *rela; - - for (rela_sec = secs; rela_sec; rela_sec = rela_sec->next) { - - if (rela_sec->sh.sh_type != SHT_RELA || - strstr(rela_sec->name, ".debug")) - continue; - - dest_sec = find_section_by_name(secs, rela_sec->name + 5); - if (!dest_sec) - ERROR("can't find text section for rela %s", - rela_sec->name); - - count = rela_sec->sh.sh_size / rela_sec->sh.sh_entsize; - - for (i = 0; i < count; i++) { - - rela = malloc(sizeof(*rela)); - memset(rela, 0, sizeof(*rela)); - - if (!gelf_getrela(rela_sec->data, i, &rela->rela)) - ELF_ERROR(elf, "gelf_getrela"); - - rela->rela_sec = rela_sec; - rela->dest_sec = dest_sec; - - off = rela->rela.r_offset; - rela->dest_sym = find_symbol_by_offset(syms, dest_sec, - off, - &rela->dest_off); - if (!rela->dest_sym) { - /* - * This means there is no symbol associated - * with the address in the destination section. - * - * We ignore mcount relocations for now. - * They'll need to be automatically regenerated - * anyway... - */ - if (!strcmp(dest_sec->name, "__mcount_loc")) { - free(rela); - continue; - } else - ERROR("%s:%d: missing symbol at offset %d", - rela_sec->name, i, off); - } - - rela->type = GELF_R_TYPE(rela->rela.r_info); - index = GELF_R_SYM(rela->rela.r_info); - - rela->src_sym = find_symbol_by_index(syms, index); - if (!rela->src_sym) - ERROR("%s:%d: missing symbol at index %d", - rela_sec->name, i, index); - - rela->src_off = rela->rela.r_addend; - - /* - * If the source symbol is actually a section, we need - * to figure out the underlying function/object. - */ - if (rela->src_sym->type == STT_SECTION) { - - const char *name = rela->src_sym->name; - - if (!strcmp(name, ".text") || - !strcmp(name, ".init.text") || - !strncmp(name, ".data", 5) || - !strcmp(name, ".bss") || - !strcmp(name, ".rodata")) { - - /* Source is a function/object */ - - /* TODO: too much indenting... */ - - /* TODO: In the case of R_X86_64_PC32, - * for find_symbol_by_offset to be - * accurate for finding the source - * symbol, we will have to disassemble - * the target function, find which - * instruction includes the target - * address, and then modify addend - * appropriately. e.g. .bss - 5. and - * _then_ call find_symbol_by_offset - * with the correct offset. - * - * But for now, it should be ok because - * we don't allow any changes (or - * additions...) to global data anyway - * and this only seems to affect .bss? - * - * but....we may need this for the - * generation phase. because when - * translating relocations we need to - * know what the source object is so we - * can look up its address in the - * vmlinux. - * - * yeah. so if the type is - * R_X86_64_PC32 we need to do this. - * examine the target location somehow, - * and convert the addend - * accordingly before calling - * find_symbol_by_offset. - * - */ - - - int addend_off = addend_offset(rela); - - - rela->src_sym = find_symbol_by_offset( - syms, - rela->src_sym->sec, - rela->rela.r_addend + addend_off, - &rela->src_off); - - rela->src_off -= addend_off; - - - if (!rela->src_sym) - ERROR("unknown reloc src " - "symbol %s+%lx", name, - rela->rela.r_addend); - - /* - printf("reloc: %s+%lx -> %s+%x\n", - name, rela->rela.r_addend, - rela->src_sym->name, - rela->src_off); - */ - - } else if (!strncmp(name, ".rodata.str", 11) || - !strcmp(name, "__ksymtab_strings")) { - /* Source is a string */ - Elf_Data *str_data = rela->src_sym->sec->data; - - rela->src_str = str_data->d_buf + - rela->rela.r_addend; - - rela->src_off = 0; - - /* - printf("reloc: %s+%lx -> %s\n", - name, rela->rela.r_addend, - rela->src_str); - */ - - } else - ERROR("don't know how to handle " - "relocation source %s", name); - } - - /* - printf("rela: %s+0x%lx to %s+0x%x\n", - rela->src_sym->name, rela->rela.r_addend, - rela->dest_sym->name, rela->dest_off); - */ - - list_add(*relas, rela); - } - } -} - - -void correlate_section_list(struct section *secs1, struct section *secs2) -{ - struct section *sec1, *sec2; - - for (sec1 = secs1; sec1; sec1 = sec1->next) { - - if (sec1->twin) - continue; - - for (sec2 = secs2; sec2; sec2 = sec2->next) { - - if (!strcmp(sec1->name, sec2->name)) { - if (sec2->twin) - ERROR("duplicate section name %s for " - "sections %zu and %zu", - sec1->name, sec1->index, - sec2->index); - sec1->twin = sec2; - sec2->twin = sec1; - break; - } - } - } -} - - -void correlate_symbol_list(struct symbol *syms1, struct symbol *syms2) -{ - struct symbol *sym1, *sym2; - - for (sym1 = syms1; sym1; sym1 = sym1->next) { - - if (sym1->twin) - continue; - - for (sym2 = syms2; sym2; sym2 = sym2->next) { - - if (!strcmp(sym1->name, sym2->name)) { - if (sym2->twin) - ERROR("duplicate symbol name %s for " - "symbols %zu and %zu", sym1->name, - sym1->index, sym2->index); - sym1->twin = sym2; - sym2->twin = sym1; - break; - } - } - } -} - -void correlate_relocation_list(struct rela *relas1, struct rela *relas2) -{ - struct rela *rela1, *rela2; - - for (rela1 = relas1; rela1; rela1 = rela1->next) { - - if (rela1->twin) - continue; - - for (rela2 = relas2; rela2; rela2 = rela2->next) { - - if (rela2->twin) - continue; - - if (rela1->rela_sec->twin != rela2->rela_sec || - rela1->dest_sec->twin != rela2->dest_sec || - rela1->src_sym->twin != rela2->src_sym || - rela1->src_off != rela2->src_off || - rela1->dest_sym->twin != rela2->dest_sym || - rela1->dest_off != rela2->dest_off || - rela1->type != rela2->type || - (rela1->src_str && - rela2->src_str && - strcmp(rela1->src_str, rela2->src_str))) - continue; - - - rela1->twin = rela2; - rela2->twin = rela1; - } - } -} - -void compare_sections(void) -{ - struct section *sec1, *sec2; - - for (sec1 = secs1; sec1; sec1 = sec1->next) { - - sec2 = sec1->twin; - if (!sec2) { - if (!strncmp(sec1->name, ".rodata.str", 11)) - continue; - - DIFF_FATAL("section %s was removed", sec1->name); - } - - if (sec1->sh.sh_type != sec2->sh.sh_type || - sec1->sh.sh_flags != sec2->sh.sh_flags || - sec1->sh.sh_addr != sec2->sh.sh_addr || - sec1->sh.sh_addralign != sec2->sh.sh_addralign || - sec1->sh.sh_entsize != sec2->sh.sh_entsize) - DIFF_FATAL("%s section header details differ", - sec1->name); - - if (sec1->sh.sh_link != SHN_UNDEF && - sec2->sh.sh_link != SHN_UNDEF && - find_section_by_index(secs1, sec1->sh.sh_link)->twin != - find_section_by_index(secs2, sec2->sh.sh_link)) - DIFF_FATAL("%s section header details differ", - sec1->name); - - if (sec1->sh.sh_type == SHT_RELA && - find_section_by_index(secs1, sec1->sh.sh_info)->twin != - find_section_by_index(secs2, sec2->sh.sh_info)) - DIFF_FATAL("%s section header details differ", - sec1->name); - - if (strstr(sec1->name, ".debug") || - strstr(sec1->name, "mcount") || - !strcmp(sec1->name, ".symtab") || - !strcmp(sec1->name, ".shstrtab") || - !strcmp(sec1->name, ".strtab") || - /* - * functions and data were already compared via - * compare_symbols - * */ - !strcmp(sec1->name, ".text") || - !strcmp(sec1->name, ".init.text") || - !strncmp(sec1->name, ".data", 5) || - !strcmp(sec1->name, ".bss") || - !strcmp(sec1->name, ".rodata") || - /* - * strings and relocations were already compared via - * compare_relocations - */ - !strncmp(sec1->name, ".rodata.str", 11) || - !strcmp(sec1->name, ".rela.text") || - !strcmp(sec1->name, ".rela.init.text") || - !strcmp(sec1->name, ".rela.rodata") || - !strncmp(sec1->name, ".rela.data", 10) || - !strncmp(sec1->name, ".rela___ksymtab_gpl", 19) || - !strncmp(sec1->name, ".rela.initcall", 14)) - continue; - - /* TODO: handle changes to .initcall* and .rela.initcall* - * somewhere */ - - /* TODO: compare ABS symbols */ - - if (sec1->sh.sh_size != sec2->sh.sh_size) - DIFF_FATAL("section %s changed", sec1->name); - - if (memcmp(sec1->data->d_buf, sec2->data->d_buf, - sec1->sh.sh_size)) - DIFF_FATAL("section %s changed", sec1->name); - } - - for (sec2 = secs2; sec2; sec2 = sec2->next) { - - if (!sec2->twin && strncmp(sec2->name, ".rodata.str", 11)) - DIFF_FATAL("section %s added", sec2->name); - } -} - -void compare_function_data(struct symbol *sym1) -{ - struct symbol *sym2 = sym1->twin; - struct symbol *target1_sym, *target2_sym; - unsigned int target1, target2, lval1, lval2; - ud_t ud1, ud2; - struct ud_operand *op1, *op2; - int rc1, rc2; - long off; - int i; - - ud_init(&ud1); - ud_set_input_buffer(&ud1, sym1->sec->data->d_buf + sym1->sym.st_value, - sym1->sym.st_size); - ud_set_mode(&ud1, 64); - - ud_init(&ud2); - ud_set_input_buffer(&ud2, sym2->sec->data->d_buf + sym2->sym.st_value, - sym2->sym.st_size); /* TODO: put st_value and st_size in local struct vars more adequately named? */ - ud_set_mode(&ud2, 64); - - while (1) { - - rc1 = ud_disassemble(&ud1); - rc2 = ud_disassemble(&ud2); - - /* - * Check if we made it to the end of one function but not the - * other. - */ - if (rc1 ^ rc2) { - DIFF(SYM_CHANGED, sym1); - break; - } - - /* Check if we're at the end of the functions. */ /* TODO this is wrong. it'll go past the end of the function to the next function. instead we need to look at the address. */ - if (!rc1) - break; - - /* Compare the instructions. */ - if (ud1.mnemonic != ud2.mnemonic) - DIFF(SYM_CHANGED, sym1); - - for (i = 0; i < 3; i++) { - op1 = &ud1.operand[i]; - op2 = &ud2.operand[i]; - if (op1->type != op2->type || - op1->size != op2->size || - op1->base != op2->base || - op1->index != op2->index || - op1->scale != op2->scale) { - DIFF(SYM_CHANGED, sym1); - goto exit; - } - - if (ud1.mnemonic == UD_Icall && ud1.br_near && - ud2.mnemonic == UD_Icall && ud2.br_near) - goto compare_call_instructions; - - if (memcmp(&op1->lval, &op2->lval, op1->size / 8)) { - DIFF(SYM_CHANGED, sym1); - goto exit; - } - } - - /* We found no differences in the instructions. Next... */ - continue; - - -compare_call_instructions: - lval1 = ud1.operand[0].lval.sdword; - lval2 = ud2.operand[0].lval.sdword; - - /* - * lvals are zero when they are relocation targets. Consider - * them as identical here, as they will be compared elsewhere - * as part of the relocation analysis. - */ - if (!lval1 && !lval2) - continue; - - /* - * If we got here, both instructions are call instructions. - * We'll need to translate their target addresses to symbols - * before comparing them, in case the functions are at - * different addresses between the two files. - */ - target1 = sym1->sym.st_value + ud1.insn_offset + - ud1.inp_ctr + lval1; - target1_sym = find_symbol_by_offset(syms1, sym1->sec, - target1, &off); - if (!target1_sym || off) - ERROR("can't find symbol for call to %d", lval1); - - target2 = sym2->sym.st_value + ud2.insn_offset + - ud2.inp_ctr + lval2; - target2_sym = find_symbol_by_offset(syms2, sym2->sec, - target2, &off); - if (!target2_sym || off) - ERROR("can't find symbol for call to %d", lval2); - - if (target1_sym->twin == target2_sym) - continue; - - DIFF(SYM_CHANGED, sym1); - break; - } - -exit: - return; - -} - -void compare_symbols(void) -{ - struct symbol *sym1, *sym2; - Elf_Data *data1, *data2; - - for (sym1 = syms1; sym1; sym1 = sym1->next) { - - if (!sym1->twin) { - - if (sym1->sym.st_shndx != SHN_UNDEF) - DIFF(SYM_REMOVED, sym1); - - continue; - } - - sym2 = sym1->twin; - - if (sym1->sym.st_info != sym2->sym.st_info || - sym1->sym.st_other != sym2->sym.st_other || /* TODO st_other could point to a section # which can change and still be the same section? */ - (sym1->sec && sym2->sec && sym1->sec->twin != sym2->sec) || - (sym1->sec && !sym2->sec) || - (sym2->sec && !sym1->sec)) - DIFF_FATAL("symbol info mismatch: %s", sym1->name); - - if (sym1->sym.st_shndx == SHN_UNDEF) - continue; - - if (sym1->type == STT_OBJECT) { - if (sym1->sym.st_size != sym2->sym.st_size) - DIFF_FATAL("object size mismatch: %s", - sym1->name); - - data1 = sym1->sec->data; - data2 = sym2->sec->data; - - if (data1->d_buf && data2->d_buf && - memcmp(data1->d_buf + sym1->sym.st_value, - data2->d_buf + sym2->sym.st_value, - sym1->sym.st_size)) { - DIFF(SYM_CHANGED, sym1); - continue; - } - - } else if (sym1->type == STT_FUNC) { - - if (sym1->sym.st_size != sym2->sym.st_size) { - DIFF(SYM_CHANGED, sym1); - continue; - } - - compare_function_data(sym1); - } - } - for (sym2 = syms2; sym2; sym2 = sym2->next) { - - if (!sym2->twin) { - - if (sym2->sym.st_shndx == SHN_UNDEF || - sym2->type == STT_SECTION) - continue; - - DIFF(SYM_ADDED, sym2); - } - } - /* TODO: - * - * don't allow any rela changes which have an rodata section - * (and/or data? bss?) as its destination. and double check that we - * have test coverage for rela of an extern global variable. - * - * also, in case of assembly you have a lot of NOTYPE symbols with - * actual section indexes and values (offsets). for now, - * DIFF_stop if we even see anything like that. - * - * when this is all done, I need to test the shit out of it! test all - * code and DIFF detection and error paths! - * */ -} - -void compare_relocation(struct rela *relas) -{ - struct rela *rela; - - for (rela = relas; rela; rela = rela->next) { - - if (rela->twin) - continue; - - /* - * We ignore added/removed/changed relocations for - * added/removed functions. All we care about are changed - * functions/objects here, because we have already looked for - * added/removed symbols previously. - */ - if (rela->dest_sym->twin) { - - switch (rela->dest_sym->type) { - case STT_FUNC: - case STT_OBJECT: - DIFF(SYM_CHANGED, rela->dest_sym); - //printf("%s:%s -> %s:%s\n", rela->rela_sec->name, rela->src_sym->name, rela->dest_sec->name, rela->dest_sym->name); - break; - default: - ERROR("%s: unknown type for destination symbol " - "%s", rela->rela_sec->name, - rela->dest_sym->name); - } - } - } -} - -void compare_relocations(void) -{ - compare_relocation(relas1); - compare_relocation(relas2); -} - -void print_changes(void) -{ - /* TODO: go through any new/removed sections and warn about them? */ - - /* TODO: fold this function into compare_symbols/compare_sections? yes.*/ - struct symbol *sym; - int changes = 0; - - /* TODO: only allow certain sections to change. and we also might want - * to add some smarts about which sections are allowed to change based - * on which symbols have changed? or maybe do that up above, when we - * call DIFF for a symbol. diff_sym could then verify that the related - * sections had changed. if not, then it's an ERROR. or it could set - * sec->changed_symbols. so that we could here check to ensure that - * !(sec->diff ^ sec->changed_symbols), so that each section change is - * associated with one or more symbol changes and vice versa. - */ - for (sym = syms1; sym; sym = sym->next) { - - if (!sym->diff) - continue; - - switch (sym->type) { - - case STT_OBJECT: - switch (sym->diff) { - case SYM_CHANGED: - DIFF_FATAL("object %s changed", sym->name); - break; - case SYM_REMOVED: - //RISKY("object %s was removed", sym->name); - DIFF_FATAL("object %s removed", sym->name); - break; - default: - ERROR("invalid diff %d for object %s", - sym->diff, sym->name); - } - break; - - case STT_FUNC: - switch (sym->diff) { - case SYM_CHANGED: - changes++; - printf("function %s changed\n", sym->name); - /* TODO: add to generated object */ - break; - case SYM_REMOVED: - RISKY("function %s was removed", sym->name); - break; - default: - ERROR("invalid diff %d for func %s", - sym->diff, sym->name); - } - break; - - default: - ERROR("invalid diff %d for symbol %s", sym->diff, - sym->name); - } - } - - for (sym = syms2; sym; sym = sym->next) { - - if (!sym->diff) - continue; - - switch (sym->type) { - case STT_OBJECT: - switch (sym->diff) { - case SYM_ADDED: - //printf("object %s added to %s\n", sym->name, - //sym->sec->name); - /* TODO: add to generated object */ - DIFF_FATAL("object %s was added", sym->name); - break; - } - break; - - case STT_FUNC: - switch (sym->diff) { - case SYM_ADDED: - printf("func %s added to %s\n", sym->name, - sym->sec->name); - changes++; - /* TODO: add to generated object */ - break; - } - break; - - default: - ERROR("invalid diff %d for symbol %s of type %d", - sym->diff, sym->name, sym->type); - } - } - - if (!changes) { - printf("no changes\n"); - exit(0); - } -} - -void correlate_symbols_to_vmlinux(void) -{ - - struct symbol *symv, *symv_inner, *sym, *symsv_global; - int diff; - - /* - * First find the local symbols. We have to do it this way so that we - * can avoid any ambiguity about which local symbols to resolve. - */ - if (syms1->type != STT_FILE) - ERROR("can't find FILE symbol"); - - for (symv = symsv; symv; symv = symv->next) { /* TODO: use find_symbol_by_name instead? */ - if (symv->type == STT_FILE && - !strcmp(symv->name, syms1->name)) { - diff = 0; - symv_inner = symv->next; - for (sym = syms1->next; sym; sym = sym->next) { - if (sym->bind != STB_LOCAL || - (sym->type != STT_FUNC && - sym->type != STT_OBJECT)) - continue; - if (symv_inner->type == STT_FILE || - strcmp(symv_inner->name, sym->name)) { - diff = 1; - break; - } - sym->twinv = symv_inner; - /* TODO: need better var names... */ - if (sym->twin) - sym->twin->twinv = symv_inner; - symv_inner = symv_inner->next; - } - /* TODO support for over 64K symbols/sections? here and elsewhere? */ - if (!diff && - (symv_inner->type == STT_FILE || - symv_inner->bind != STB_LOCAL)) { - goto success; - } - } - } - ERROR("can't find symbols from %s in %s", args.args[0], args.vmlinux); - -success: - - /* - * Now find the global symbols. - */ - for (symsv_global = symsv; - symsv_global && symsv_global->bind == STB_LOCAL; - symsv_global = symsv_global->next) - ; - - for (sym = syms2; sym; sym = sym->next) { - - if (sym->type == STT_FILE || sym->type == STT_SECTION) - continue; - - if (sym->twinv) - continue; - - /* new symbols won't have a corresponding vmlinux symbol */ - /* TODO what about new weak symbols? */ - if (sym->bind == STB_LOCAL && !sym->twin) - continue; - - /* TODO: make sure this works for local global symbols */ - - sym->twinv = find_symbol_by_name(symsv_global, sym->name); - if (!sym->twinv) - ERROR("can't find %s in %s", sym->name, args.vmlinux); - - /* - printf("found %s (%lx) at %lx\n", sym->name, sym->sym.st_value, - sym->twinv->sym.st_value); - */ - } -} - -void init_output_section_list() -{ - struct section *oldsec, *newsec; - int index; - - index = 0; - for (oldsec = secs2; oldsec; oldsec = oldsec->next) { - - if (strcmp(oldsec->name, ".text") && - strcmp(oldsec->name, ".rela.text") && - strncmp(oldsec->name, ".rodata.str", 11) && - strcmp(oldsec->name, ".shstrtab") && - strcmp(oldsec->name, ".symtab") && - strcmp(oldsec->name, ".strtab") && - //strcmp(oldsec->name, "__mcount_loc") && TODO - //strcmp(oldsec->name, ".rela__mcount_loc") && TODO - strcmp(oldsec->name, ".comment") && - strcmp(oldsec->name, ".note.GNU-stack")) - continue; - - newsec = malloc(sizeof(*newsec)); - memset(newsec, 0, sizeof(*newsec)); - - newsec->sh = oldsec->sh; - - newsec->data = malloc(sizeof(*newsec->data)); - *newsec->data = *oldsec->data; - - newsec->name = oldsec->name; - - oldsec->twino = newsec; - newsec->twino = oldsec; - - newsec->index = ++index; - - list_add(secso, newsec); - } -} - -void init_output_symbol_list(void) -{ - struct symbol *oldsym, *newsym; - int index; - - index = 0; - for (oldsym = syms2; oldsym; oldsym = oldsym->next) { - if (!oldsym->diff && - oldsym->type != STT_FILE && - !(oldsym->type == STT_SECTION && oldsym->sec->twino)) - continue; - - newsym = malloc(sizeof(*newsym)); - memset(newsym, 0, sizeof(*newsym)); - - newsym->sym = oldsym->sym; - newsym->name = oldsym->name; - if (oldsym->sym.st_shndx != SHN_ABS) - newsym->sec = oldsym->sec->twino; - newsym->index = ++index; - newsym->type = oldsym->type; - newsym->bind = oldsym->bind; - newsym->diff = oldsym->diff; - oldsym->twino = newsym; - newsym->twino = oldsym; - - if (newsym->sec) - newsym->sym.st_shndx = newsym->sec->index; - newsym->sym.st_info = GELF_ST_INFO(STB_LOCAL, newsym->type); - - list_add(symso, newsym); - } -} - -size_t strtab_add(const char *strtab, const char *str) -{ - struct section *sec; - size_t ndx; - void *oldbuf; - - sec = find_section_by_name(secso, strtab); - ndx = sec->data->d_size; - sec->sh.sh_size = sec->data->d_size = ndx + strlen(str) + 1; - oldbuf = sec->data->d_buf; - sec->data->d_buf = malloc(sec->data->d_size); - memcpy(sec->data->d_buf, oldbuf, sec->data->d_size); - strcpy(sec->data->d_buf + ndx, str); - - return ndx; -} - -int main(int argc, char *argv[]) -{ - GElf_Ehdr eh1, eh2, eho; - size_t phnum1, phnum2, shstrndx1, shstrndx2; - int fd1, fd2, fdv, fdo; /*TODO: rename */ - int index, symtab_index; - struct symbol *sym; - struct section *sec; - struct rela *rela; - GElf_Rela *newrela; - int rela_kpatch_relas_sec_strndx, kpatch_relas_sec_strndx; //FIXME int? - int num_patches; - int kpatch_patches_sec_strndx, rela_kpatch_patches_sec_strndx; - int num_kpatch_relas; //FIXME int? - size_t kpatch_rela_size; - struct symbol *newsym; - Elf_Data *newdata; - struct kpatch_patch *patch; - - argp_parse(&argp, argc, argv, 0, NULL, &args); - - elf_version(EV_CURRENT); - elf1 = elf_open(args.args[0], &fd1); - elf2 = elf_open(args.args[1], &fd2); - - if (!gelf_getehdr(elf1, &eh1)) - ELF_ERROR(elf1, "gelf_getehdr"); - - if (!gelf_getehdr(elf2, &eh2)) - ELF_ERROR(elf2, "gelf_getehdr"); - - if (memcmp(eh1.e_ident, eh2.e_ident, EI_NIDENT) || - eh1.e_type != eh2.e_type || - eh1.e_machine != eh2.e_machine || - eh1.e_version != eh2.e_version || - eh1.e_entry != eh2.e_entry || - eh1.e_phoff != eh2.e_phoff || - eh1.e_flags != eh2.e_flags || - eh1.e_ehsize != eh2.e_ehsize || - eh1.e_phentsize != eh2.e_phentsize || - eh1.e_shentsize != eh2.e_shentsize) - DIFF_FATAL("ELF headers differ"); - - - if (elf_getphdrnum(elf1, &phnum1)) - ELF_ERROR(elf1, "elf_getphdrnum"); - - if (elf_getphdrnum(elf2, &phnum2)) - ELF_ERROR(elf2, "elf_getphdrnum"); - - if (phnum1 || phnum2) - DIFF_FATAL("program header counts are nonzero"); - - -#if 0 - if (elf_getshdrnum(elf1, &shnum1)) - ELF_ERROR(elf1, "elf_getshdrnum"); - - if (elf_getshdrnum(elf2, &shnum2)) - ELF_ERROR(elf2, "elf_getshdrnum"); - - if (shnum1 != shnum2) - DIFF_FATAL("section counts differ"); -#endif /* TODO remove this code */ - - - if (elf_getshdrstrndx(elf1, &shstrndx1)) - ELF_ERROR(elf1, "elf_getshdrstrndx"); - - if (elf_getshdrstrndx(elf2, &shstrndx2)) - ELF_ERROR(elf2, "elf_getshdrstrndx"); - - init_section_list(elf1, &secs1); - init_symbol_list(elf1, secs1, &syms1); - init_rela_list(elf1, secs1, syms1, &relas1); - - init_section_list(elf2, &secs2); - init_symbol_list(elf2, secs2, &syms2); - init_rela_list(elf2, secs2, syms2, &relas2); - - correlate_section_list(secs1, secs2); - correlate_section_list(secs2, secs1); - - correlate_symbol_list(syms1, syms2); - correlate_symbol_list(syms2, syms1); - - correlate_relocation_list(relas1, relas2); - correlate_relocation_list(relas2, relas1); - - compare_sections(); - compare_symbols(); - compare_relocations(); - - print_changes(); - - elfv = elf_open(args.vmlinux, &fdv); - init_section_list(elfv, &secsv); - init_symbol_list(elfv, secsv, &symsv); - correlate_symbols_to_vmlinux(); - - /* TODO: do it twice to ensure there's only one match, but be careful b/c twinv is getting set in this function */ - - /* TODO: at beginning, ensure only 1 FILE symbol */ - - /* TODO: what if the needed global symbol is a new func in another patched .o????? maybe we need to combine all patched.o's into a big .o before we do the address resolution...? */ - - /* TODO: drop any changes made to .init.text, .rela.init.text, .initcall*, .rela.initcall*, .todata, .rela.rodata, *debug*, but make RISKY (in some cases) all we really care about is .text, .rela.text, and .rodata.str*...? */ - - /* TODO: make sure relocations with data as source, such as ".bss - 4", translate properly during the generation. - */ - - /* TODO: too many strcmp's with section names. we need to encapsulate that section knowledge in one function, which will be the only place that knows which sections are used for what. diff'ing, generating, ignore, straight copy, etc. */ - - /* TODO: why don't we just fail on any changes to initcalls, data, etc. reduce the places where we put risky, since risky might not be noticed. then the user can hopefully modify the patch to make it safe. */ - - - init_output_section_list(); - init_output_symbol_list(); - - - kpatch_relas_sec_strndx = strtab_add(".shstrtab", "__kpatch_relas"); - rela_kpatch_relas_sec_strndx = strtab_add(".shstrtab", ".rela__kpatch_relas"); - kpatch_patches_sec_strndx = strtab_add(".shstrtab", "__kpatch_patches"); - rela_kpatch_patches_sec_strndx = strtab_add(".shstrtab", ".rela__kpatch_patches"); - - - symtab_index = find_section_by_name(secso, ".symtab")->index; - - - /* update .text relocations */ - sec = find_section_by_name(secso, ".rela.text"); - sec->sh.sh_link = symtab_index; - sec->data->d_buf = malloc(sec->sh.sh_size); - sec->sh.sh_size = sec->data->d_size = 0; - sec->sh.sh_info = find_section_by_name(secso, ".text")->index; - - index = 0; - num_kpatch_relas = 0; - for (rela = relas2; rela; rela = rela->next) { - /* - * for each relas - * if rela target isn't in a changed symbol, drop it. - * if local symbol and symbol changed, keep rela. - * or if source is from .rodata.str, keep rela. - * else put in kpatch_rela. - */ - - - /* TODO - * or (!rela->src_sym->twino || !rela->dest_sym->twino) - */ - if (!rela->dest_sym->diff) - continue; - - if (rela->src_sym->bind != STB_LOCAL || - rela->src_sym->type == STT_OBJECT) { - rela->kpatch_rela = malloc(sizeof(*rela->kpatch_rela)); - rela->kpatch_rela->type = rela->type; - rela->kpatch_rela->dest = 0; - rela->kpatch_rela->src = rela->src_sym->twinv->sym.st_value + rela->src_off; - num_kpatch_relas++; - continue; - } - - sec->data->d_size = sec->sh.sh_size = (index + 1) * sec->sh.sh_entsize; - newrela = sec->data->d_buf + (index * sizeof(*newrela)); - memcpy(newrela, &rela->rela, sizeof(*newrela)); - newrela->r_info = GELF_R_INFO(rela->src_sym->twino->index, rela->type); - - index++; - } - - - /* add __kpatch_relas section */ - sec = malloc(sizeof(*sec)); - memset(sec, 0, sizeof(*sec)); - - kpatch_rela_size = sizeof(struct kpatch_rela); - sec->name = "__kpatch_relas"; - sec->index = list_size(secso) + 1; - sec->data = malloc(sizeof(Elf_Data)); - sec->data->d_size = sec->sh.sh_size = num_kpatch_relas * kpatch_rela_size; - sec->data->d_buf = malloc(sec->data->d_size); - sec->data->d_type = ELF_T_BYTE; /* TODO? */ - sec->sh.sh_type = SHT_PROGBITS; - sec->sh.sh_name = kpatch_relas_sec_strndx; - sec->sh.sh_addralign = 8; - sec->sh.sh_entsize = kpatch_rela_size; - sec->sh.sh_flags = SHF_ALLOC; - index = 0; - for (rela = relas2; rela; rela = rela->next) { - if (!rela->kpatch_rela) - continue; - memcpy(sec->data->d_buf + (index * kpatch_rela_size), - rela->kpatch_rela, kpatch_rela_size); - index++; - } - list_add(secso, sec); - - - /* TODO verify somewhere that all inputs' data are only one d_buf */ - - - /* add __kpatch_relas symbol */ /* TODO necessary? */ - newsym = malloc(sizeof(*newsym)); - memset(newsym, 0, sizeof(*newsym)); - newsym->name = "__kpatch_relas"; - newsym->sec = find_section_by_name(secso, "__kpatch_relas"); - newsym->index = list_size(symso) + 1; - newsym->sym.st_name = 0; - newsym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION); - newsym->sym.st_other = 0; - newsym->sym.st_shndx = newsym->sec->index; - newsym->sym.st_value = 0; - newsym->sym.st_size = 0; - list_add(symso, newsym); - - - - - /* add .rela__kpatch_relas section */ - sec = malloc(sizeof(*sec)); - memset(sec, 0, sizeof(*sec)); - - sec->name = ".rela__kpatch_relas"; - sec->index = list_size(secso) + 1; - sec->data = malloc(sizeof(Elf_Data)); - sec->data->d_buf = malloc(num_kpatch_relas * sizeof(GElf_Rela)); - sec->data->d_type = ELF_T_RELA; - sec->data->d_size = sec->sh.sh_size = 0; - sec->sh.sh_type = SHT_RELA; - sec->sh.sh_name = rela_kpatch_relas_sec_strndx; - sec->sh.sh_addralign = 8; - sec->sh.sh_entsize = sizeof(GElf_Rela); - sec->sh.sh_link = symtab_index; - sec->sh.sh_info = find_section_by_name(secso, "__kpatch_relas")->index; - index = 0; - for (rela = relas2; rela; rela = rela->next) { - if (!rela->kpatch_rela) - continue; - sec->data->d_size = sec->sh.sh_size = (index + 1) * sec->sh.sh_entsize; /* TODO - get rid of this incremental adding shit */ - newrela = sec->data->d_buf + (index * sizeof(*newrela)); - newrela->r_offset = index * kpatch_rela_size; - newrela->r_info = GELF_R_INFO(find_symbol_by_name(symso, rela->dest_sym->sec->name)->index, - R_X86_64_64); - newrela->r_addend = rela->dest_sym->sym.st_value + rela->dest_off; - index++; - } - list_add(secso, sec); - - - /* add __kpatch_patches section */ - num_patches = 0; - for (sym = symso; sym; sym = sym->next) - if (sym->diff) - num_patches++; - - sec = malloc(sizeof(*sec)); - memset(sec, 0, sizeof(*sec)); - - sec->name = "__kpatch_patches"; - sec->index = list_size(secso) + 1; - sec->data = malloc(sizeof(Elf_Data)); - sec->data->d_size = sec->sh.sh_size = num_patches * sizeof(struct kpatch_patch); - sec->data->d_buf = malloc(sec->data->d_size); - sec->data->d_type = ELF_T_BYTE; - sec->sh.sh_type = SHT_PROGBITS; - sec->sh.sh_name = kpatch_patches_sec_strndx; - sec->sh.sh_entsize = sizeof(struct kpatch_patch); - sec->sh.sh_addralign = 8; - sec->sh.sh_flags = SHF_ALLOC; - index = 0; - for (sym = symso; sym; sym = sym->next) { - if (!sym->diff) - continue; - patch = sec->data->d_buf + (index * sec->sh.sh_entsize); - patch->orig = sym->twino->twinv->sym.st_value; - patch->orig_end = patch->orig + sym->sym.st_size; - patch->new = 0; - index++; - } - list_add(secso, sec); - - - /* add .rela__kpatch_patches section */ - sec = malloc(sizeof(*sec)); - memset(sec, 0, sizeof(*sec)); - - sec->name = ".rela__kpatch_patches"; - sec->index = list_size(secso) + 1; - sec->data = malloc(sizeof(Elf_Data)); - sec->data->d_buf = malloc(num_patches * sizeof(GElf_Rela)); - sec->data->d_type = ELF_T_RELA; - sec->data->d_size = sec->sh.sh_size = 0; - sec->sh.sh_type = SHT_RELA; - sec->sh.sh_name = rela_kpatch_patches_sec_strndx; - sec->sh.sh_addralign = 8; - sec->sh.sh_entsize = sizeof(GElf_Rela); - sec->sh.sh_link = symtab_index; - sec->sh.sh_info = find_section_by_name(secso, "__kpatch_patches")->index; - index = 0; - for (sym = symso; sym; sym = sym->next) { - if (!sym->diff) - continue; - sec->data->d_size = sec->sh.sh_size = (index + 1) * sec->sh.sh_entsize; - newrela = sec->data->d_buf + (index * sizeof(*newrela)); - newrela->r_offset = index * sizeof(struct kpatch_patch); - newrela->r_info = GELF_R_INFO(sym->index, R_X86_64_64); - newrela->r_addend = 0; - } - list_add(secso, sec); - - -#if 0 /* TODO enable mcount */ - /* fix .rela__mcount_loc link */ - sec = find_section_by_name(secso, ".rela__mcount_loc"); - sec->sh.sh_link = symtab_index; - sec->sh.sh_info = find_section_by_name(secso, "__mcount_loc")->index; -#endif - - - /* update symbol table section */ - sec = find_section_by_name(secso, ".symtab"); - sec->sh.sh_link = find_section_by_name(secso, ".strtab")->index; - sec->data->d_buf = malloc((list_size(secso) + 1) * sec->sh.sh_entsize); - sec->sh.sh_size = sec->data->d_size = 4; /* keep sym 0 */ - memcpy(sec->data->d_buf, sec->twino->data->d_buf, 4); - sec->sh.sh_info = list_size(symso) + 1; - for (sym = symso; sym; sym = sym->next) { - sec->data->d_size = sec->sh.sh_size = (sym->index + 1) * sec->sh.sh_entsize; - ((Elf64_Sym *) sec->data->d_buf)[sym->index] = sym->sym; - } - - - /* create elf output file */ - fdo = creat(args.outfile, 0777); - if (fdo == -1) - error(1, errno, "create of %s failed", args.outfile); - - elfo = elf_begin(fdo, ELF_C_WRITE, NULL); - if (!elfo) - error(1, 0, "elf_begin failed for %s: %s", args.outfile, - elf_errmsg(-1)); - - if (!gelf_newehdr(elfo, gelf_getclass(elf2))) - ELF_ERROR(elfo, "gelf_newehdr"); - - if (!gelf_getehdr(elfo, &eho)) - ELF_ERROR(elfo, "gelf_getehdr"); - - eho.e_ident[EI_DATA] = eh2.e_ident[EI_DATA]; - eho.e_machine = eh2.e_machine; - eho.e_type = eh2.e_type; - eho.e_version = EV_CURRENT; - eho.e_shstrndx = find_section_by_name(secso, ".shstrtab")->index; - - for (sec = secso; sec; sec = sec->next) { - - sec->sec = elf_newscn(elfo); - if (!sec->sec) - ELF_ERROR(elfo, "elf_newscn"); - - newdata = elf_newdata(sec->sec); - if (!newdata) - ELF_ERROR(elfo, "elf_newdata"); - - newdata->d_buf = sec->data->d_buf; - newdata->d_type = sec->data->d_type; - newdata->d_size = sec->data->d_size; - - sec->data = newdata; - - if (!elf_flagdata(sec->data, ELF_C_SET, ELF_F_DIRTY)) - ELF_ERROR(elfo, "elf_flagdata"); - - if (!gelf_update_shdr(sec->sec, &sec->sh)) - ELF_ERROR(elfo, "gelf_update_shdr"); - } - - if (!gelf_update_ehdr(elfo, &eho)) - ELF_ERROR(elfo, "gelf_update_ehdr"); - - if (elf_update(elfo, ELF_C_WRITE) < 0) - ELF_ERROR(elfo, "elf_update"); - - - return 0; -} diff --git a/kpatch-files/Makefile b/kpatch-files/Makefile new file mode 100644 index 0000000..92d3bee --- /dev/null +++ b/kpatch-files/Makefile @@ -0,0 +1,20 @@ +KPATCH_BUILD = /home/sjennings/projects/linux +KPATCH_OBJ = /home/sjennings/projects/obj +KPATCH_MODULES = $(PWD) +KPATCH_MAKE = $(MAKE) -C $(KPATCH_BUILD) O=$(KPATCH_OBJ) M=$(KPATCH_MODULES) + +obj-m += kpatch-patch.o + +kpatch-patch-objs += kpatch-patch-hook.o kpatch.lds ../output.kpatch_gen + +all: kpatch-patch.ko + +kpatch-patch.ko: + $(KPATCH_MAKE) kpatch-patch.ko + +kpatch-patch-hook.o: kpatch-patch-hook.c + $(KPATCH_MAKE) kpatch-patch-hook.o + +clean: + $(RM) -Rf .*.o.cmd .*.ko.cmd .tmp_versions *.o *.ko *.mod.c \ + Module.symvers diff --git a/kpatch-files/kpatch-patch-hook.c b/kpatch-files/kpatch-patch-hook.c new file mode 100644 index 0000000..dca0fd0 --- /dev/null +++ b/kpatch-files/kpatch-patch-hook.c @@ -0,0 +1,24 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include "../kpatch-kmod/kpatch.h" + +extern char __kpatch_patches, __kpatch_patches_end; + +static int __init patch_init(void) +{ + printk("patch loading\n"); + return kpatch_register(THIS_MODULE, &__kpatch_patches, + &__kpatch_patches_end); +} + +static void __exit patch_exit(void) +{ + printk("patch unloading\n"); + kpatch_unregister(THIS_MODULE); +} + +module_init(patch_init); +module_exit(patch_exit); +MODULE_LICENSE("GPL"); diff --git a/kpatch-files/kpatch.lds b/kpatch-files/kpatch.lds new file mode 100644 index 0000000..80bee83 --- /dev/null +++ b/kpatch-files/kpatch.lds @@ -0,0 +1,2 @@ +__kpatch_patches = ADDR(.patches); +__kpatch_patches_end = ADDR(.patches) + SIZEOF(.patches); diff --git a/kpatch-gcc b/kpatch-gcc deleted file mode 100755 index a243e57..0000000 --- a/kpatch-gcc +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# vim: tabstop=4 shiftwidth=4 expandtab -set -o nounset -set -o errexit -set -o pipefail - -SCRIPT="`basename $BASH_SOURCE`" - -arg1="$1" -shift -args=() -i=0 -for arg in "$@"; do - args[$i]="$arg" - i=$((i + 1)) -done -outfile= - -die () -{ - echo "$SCRIPT: error: $*" >&2 - exit 1 -} - -if [ -f kpatch-in-progress ] && [ "$arg1" = "gcc" ]; then - while [ "$#" -gt 0 ]; do - if [ "$1" = "-o" ]; then - case "$2" in - *.mod.o|*built-in.o|vmlinux.o|.tmp_kallsyms1.o|.tmp_kallsyms2.o|init/version.o|arch/x86/boot/version.o|arch/x86/boot/compressed/eboot.o|arch/x86/boot/header.o|arch/x86/boot/compressed/efi_stub_64.o|arch/x86/boot/compressed/piggy.o) - ;; - *.o) - cp -f "$2" "$2.kpatch_orig" - ;; - esac - break - fi - shift - done -fi - -#exec $arg1 $args -exec "$arg1" "${args[@]}" diff --git a/kpatch-kmod/Makefile b/kpatch-kmod/Makefile new file mode 100644 index 0000000..4fb2baf --- /dev/null +++ b/kpatch-kmod/Makefile @@ -0,0 +1,16 @@ +KPATCH_BUILD = /home/sjennings/projects/linux +KPATCH_OBJ = /home/sjennings/projects/obj +KPATCH_MODULES = $(PWD) +KPATCH_MAKE = $(MAKE) -C $(KPATCH_BUILD) O=$(KPATCH_OBJ) M=$(KPATCH_MODULES) + +obj-m := kpatch.o +kpatch-y := base.o trampoline.o + +kpatch.ko: base.c trampoline.S + $(KPATCH_MAKE) kpatch.ko + +all: kpatch.ko + +clean: + $(RM) -Rf .*.o.cmd .*.ko.cmd .tmp_versions *.o *.ko *.mod.c \ + Module.symvers diff --git a/kmod/base.c b/kpatch-kmod/base.c similarity index 71% rename from kmod/base.c rename to kpatch-kmod/base.c index f630e88..cac3344 100644 --- a/kmod/base.c +++ b/kpatch-kmod/base.c @@ -1,14 +1,22 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include +#include #include #include #include "kpatch.h" +#define KPATCH_MAX_FUNCS 256 struct kpatch_func kpatch_funcs[KPATCH_MAX_FUNCS+1]; static int kpatch_num_registered; +/* from trampoline.S */ +extern void kpatch_trampoline(unsigned long ip, unsigned long parent_ip, + struct ftrace_ops *op, struct pt_regs *regs); + /* * Deal with some of the peculiarities caused by the trampoline being called * from __ftrace_ops_list_func instead of directly from ftrace_regs_caller. @@ -134,73 +142,25 @@ out: static struct ftrace_ops kpatch_ftrace_ops __read_mostly = { .func = kpatch_trampoline, - .flags = FTRACE_OPS_FL_NORETURN | FTRACE_OPS_FL_SAVE_REGS, + .flags = FTRACE_OPS_FL_SAVE_REGS, }; -int kpatch_register(struct module *mod, void *kpatch_relas, - void *kpatch_relas_end, void *kpatch_patches, +int kpatch_register(struct module *mod, void *kpatch_patches, void *kpatch_patches_end) { int ret = 0; int ret2; - int num_relas; - struct kpatch_rela *relas; int i; - u64 val; - void *loc; - int size; int num_patches; struct kpatch_patch *patches; struct kpatch_func *funcs, *f; - num_relas = (kpatch_relas_end - kpatch_relas) / sizeof(*relas); - relas = kpatch_relas; + pr_err("loading patch module \"%s\"", mod->name); num_patches = (kpatch_patches_end - kpatch_patches) / sizeof(*patches); patches = kpatch_patches; - /* FIXME consider change dest/src to loc/val */ - /* TODO: ensure dest value is all zeros before touching it, and that it's within the module bounds */ - for (i = 0; i < num_relas; i++) { - - switch (relas[i].type) { - case R_X86_64_PC32: - loc = (void *)relas[i].dest; - val = (u32)(relas[i].src - relas[i].dest); - size = 4; - break; - case R_X86_64_32S: - loc = (void *)relas[i].dest; - val = (s32)relas[i].src; - size = 4; - break; - default: - printk("unsupported rela type %ld for " - "0x%lx <- 0x%lx at index %d\n", - relas[i].type, relas[i].dest, - relas[i].src, i); - ret = -EINVAL; - goto out; - } - //printk("%p <- %lx\n", loc, val); - //printk("%lx\n", (unsigned long)__va(__pa((unsigned long)loc))); - //loc = __va(__pa((unsigned long)loc)); - /* TODO: safe to assume it was ro to start with? */ - set_memory_rw((unsigned long)loc & PAGE_MASK, 1); - ret = probe_kernel_write(loc, &val, size); - set_memory_ro((unsigned long)loc & PAGE_MASK, 1); - if (ret) - goto out; - /* TODO: sync_core? */ - /* TODO: understand identity mapping vs text mapping */ - } - - /* TODO: mutex here? */ - - /* TODO verify num_patches is within acceptable bounds */ - - funcs = kmalloc((num_patches + 1) * sizeof(*funcs), GFP_KERNEL); /*TODO: error handling, free, etc */ for (i = 0; i < num_patches; i++) { @@ -209,8 +169,6 @@ int kpatch_register(struct module *mod, void *kpatch_relas, funcs[i].new_func_addr = patches[i].new; funcs[i].mod = mod; funcs[i].old_func_name = "TODO"; - /* TODO: need old_func_addr_end too */ - /* TODO: verify name/address with kallsyms */ /* Do any needed incremental patching. */ for (f = kpatch_funcs; f->old_func_name; f++) { @@ -231,46 +189,6 @@ int kpatch_register(struct module *mod, void *kpatch_relas, } memset(&funcs[num_patches], 0, sizeof(*funcs)); -#if 0 - /* Find the functions to be replaced. */ - for (f = funcs; f->old_func_name; f++) { - /* TODO: verify it's a function and look for duplicate symbol names */ - /* TODO: use pre-generated func address? if using exact kernel - * is a requirement?*/ - f->old_func_addr = kallsyms_lookup_name(f->old_func_name); - if (!f->old_func_addr) { - printk("kpatch: can't find function '%s'\n", - f->old_func_name); - ret = -ENXIO; - goto out; - } - - - if (!kallsyms_lookup_size_offset(f->old_func_addr, &size, - &offset)) { - printk("kpatch: no size for function '%s'\n", - f->old_func_name); - - ret = -ENXIO; - goto out; - } - /* TODO: check ret, size, offset */ - - f->old_func_addr_end = f->old_func_addr + size; - - ret = ftrace_set_filter_ip(&kpatch_ftrace_ops, f->old_func_addr, - 0, 0); - if (ret) { - printk("kpatch: can't set ftrace filter at " - "%lx '%s' (%d)\n", - f->old_func_addr, f->old_func_name, ret); - goto out; - } - } - - /* TODO: global variable/array locking */ -#endif - /* Register the ftrace trampoline if it hasn't been done already. */ if (!kpatch_num_registered++) { ret = register_ftrace_function(&kpatch_ftrace_ops); diff --git a/kpatch-kmod/kpatch.h b/kpatch-kmod/kpatch.h new file mode 100644 index 0000000..1d95be3 --- /dev/null +++ b/kpatch-kmod/kpatch.h @@ -0,0 +1,28 @@ +#ifndef _KPATCH_H_ +#define _KPATCH_H_ + +struct kpatch_func { + unsigned long old_func_addr; + unsigned long new_func_addr; + char *old_func_name; + unsigned long old_func_addr_end; + struct module *mod; +}; + +struct kpatch_rela { + unsigned long dest; + unsigned long src; + unsigned long type; +}; + +struct kpatch_patch { + unsigned long new; + unsigned long orig; + unsigned long orig_end; +}; + +int kpatch_register(struct module *mod, void *kpatch_patches, + void *kpatch_patches_end); +int kpatch_unregister(struct module *mod); + +#endif /* _KPATCH_H_ */ diff --git a/kmod/trampoline.S b/kpatch-kmod/trampoline.S similarity index 100% rename from kmod/trampoline.S rename to kpatch-kmod/trampoline.S diff --git a/scripts/kpatch.service b/scripts/kpatch.service new file mode 100644 index 0000000..7c92519 --- /dev/null +++ b/scripts/kpatch.service @@ -0,0 +1,9 @@ +[Unit] +Description=kpatch -- apply all enabled dynamic kernel patches +Before= +OnFailure= + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/bin/kpatch load --all diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..1d9685b --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,13 @@ +all: create-diff-object add-patches-section link-vmlinux-syms + +create-diff-object: create-diff-object.c + $(CC) $(CFLAGS) -o $@ $^ -lelf + +add-patches-section: add-patches-section.c + $(CC) $(CFLAGS) -o $@ $^ -lelf + +link-vmlinux-syms: link-vmlinux-syms.c + $(CC) $(CFLAGS) -o $@ $^ -lelf + +clean: + $(RM) -Rf create-diff-object add-patches-section link-vmlinux-syms diff --git a/tools/add-patches-section b/tools/add-patches-section new file mode 100755 index 0000000..514a2fc Binary files /dev/null and b/tools/add-patches-section differ diff --git a/tools/add-patches-section.c b/tools/add-patches-section.c new file mode 100644 index 0000000..eed6be3 --- /dev/null +++ b/tools/add-patches-section.c @@ -0,0 +1,426 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kpatch-kmod/kpatch.h" + +#define ERROR(format, ...) \ + error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) + +struct section { + Elf_Scn *scn; + GElf_Shdr sh; +}; + +enum symaction { + NOOP, /* do nothing, default */ + PATCH, /* sym is a patched function */ + LINK, /* sym is a non-exported global sym */ +}; + +struct sym { + struct sym *next; + GElf_Sym sym; + char *name; + int index; + enum symaction action; + unsigned long vm_addr; + size_t vm_len; +}; + +struct symlist { + struct sym *head; + size_t len; +}; + +struct elf { + Elf *elf; + int fd; + size_t shstrndx; + struct section symtab, shstrtab; +}; + +#define for_each_sym(list, iter) \ + for((iter) = (list)->head; (iter); (iter) = (iter)->next) + +enum elfmode { + RDONLY, + RDWR +}; + +static void open_elf(char *path, enum elfmode elfmode, struct elf *elf) +{ + mode_t mode; + Elf_Cmd cmd; + + switch(elfmode) { + case RDONLY: + mode = O_RDONLY; + cmd = ELF_C_READ_MMAP; + break; + case RDWR: + mode = O_RDWR; + cmd = ELF_C_RDWR; + break; + } + + if ((elf->fd = open(path, mode, 0)) < 0) + ERROR("open"); + + elf->elf = elf_begin(elf->fd, cmd, NULL); + if (!elf->elf) { + printf("%s\n", elf_errmsg(-1)); + ERROR("elf_begin"); + } + + if (elf_getshdrstrndx(elf->elf, &elf->shstrndx)) + ERROR("elf_getshdrstrndx"); +} + +static void insert_sym(struct symlist *list, GElf_Sym *sym, char *name, + int index) +{ + struct sym *newsym; + + newsym = malloc(sizeof(*newsym)); + if (!newsym) + ERROR("malloc"); + memset(newsym, 0, sizeof(*newsym)); + newsym->sym = *sym; + newsym->name = name; + newsym->index = index; + + newsym->next = list->head; + list->head = newsym; +} + +static void find_section_by_name(struct elf *elf, char *name, struct section *sec) +{ + Elf_Scn *scn = NULL; + GElf_Shdr sh; + char *secname; + + while (scn = elf_nextscn(elf->elf, scn)) { + if (!gelf_getshdr(scn, &sh)) + ERROR("gelf_getshdr"); + + secname = elf_strptr(elf->elf, elf->shstrndx, sh.sh_name); + if (!secname) + ERROR("elf_strptr scn"); + + if (!strcmp(secname, name)) + break; + } + + if (!scn) + ERROR("no section %s found", name); + + sec->scn = scn; + sec->sh = sh; +} + +static void create_symlist(struct elf *elf, struct symlist *symlist) +{ + Elf_Scn *scn = elf->symtab.scn; + GElf_Shdr *sh = &elf->symtab.sh; + GElf_Sym sym; + Elf_Data *data; + char *name; + int i; + + /* get symtab data buffer */ + data = elf_getdata(scn, NULL); + if (!data) + ERROR("elf_getdata"); + + /* find (local) function symbols + * NOTE: If the function symbol is in the kpatch-gen file, it needs + * to be patched. If the function didn't need to be patched, + * it wouldn't have been incldued in the kpatch-gen file. + */ + symlist->len = sh->sh_size / sh->sh_entsize; + for (i = 0; i < symlist->len; i++) { + if (!gelf_getsym(data, i, &sym)) + ERROR("gelf_getsym"); + + name = elf_strptr(elf->elf, sh->sh_link, sym.st_name); + if(!name) + ERROR("elf_strptr sym"); + + insert_sym(symlist, &sym, name, i); + } +} + +static struct sym *find_symbol_by_name(struct symlist *list, char *name) +{ + struct sym *cur; + + for_each_sym(list, cur) + if (!strcmp(cur->name, name)) + return cur; + return NULL; +} + +int main(int argc, char **argv) +{ + struct symlist symlist, symlistv; + struct sym *cur, *vsym; + struct elf elf, elfv; + char name[255]; + void *buf; + struct kpatch_patch *patches_data; + GElf_Rela *relas_data; + int patches_nr = 0, i, patches_size, relas_size, len; + int patches_offset, relas_offset, patches_index, relas_index; + struct section symtab; + Elf_Scn *scn; + Elf_Data *data; + GElf_Shdr sh, *shp; + GElf_Ehdr eh; + GElf_Sym sym; + + /* set elf version (required by libelf) */ + if (elf_version(EV_CURRENT) == EV_NONE) + ERROR("elf_version"); + + memset(&elf, 0, sizeof(elf)); + memset(&elfv, 0, sizeof(elfv)); + open_elf(argv[1], RDWR, &elf); + open_elf(argv[2], RDONLY, &elfv); + + find_section_by_name(&elf, ".symtab", &(elf.symtab)); + find_section_by_name(&elfv, ".symtab", &(elfv.symtab)); + + find_section_by_name(&elf, ".shstrtab", &(elf.shstrtab)); + + memset(&symlist, 0, sizeof(symlist)); + memset(&symlistv, 0, sizeof(symlistv)); + create_symlist(&elf, &symlist); + create_symlist(&elfv, &symlistv); + + /* lookup patched functions in vmlinux */ + for_each_sym(&symlist, cur) { + if (GELF_ST_TYPE(cur->sym.st_info) != STT_FUNC) + continue; + + printf("found patched function %s\n", cur->name); + + vsym = find_symbol_by_name(&symlistv, cur->name); + if (!vsym) + ERROR("couldn't find patched function in vmlinux"); + cur->vm_addr = vsym->sym.st_value; + cur->vm_len = vsym->sym.st_size; + cur->action = PATCH; + printf("original function at address %016lx (length %d)\n", + cur->vm_addr, cur->vm_len); + patches_nr++; + } + +#if 0 + /* lookup non-exported globals and insert vmlinux address */ + for_each_sym(&symlist, cur) { + if (GELF_ST_TYPE(cur->sym.st_info) != STT_NOTYPE || + GELF_ST_BIND(cur->sym.st_info) != STB_GLOBAL) + continue; + + printf("found global symbol %s\n", cur->name); + sprintf(name, "__kstrtab_%s", cur->name); + vsym = find_symbol_by_name(&symlistv, name); + if (vsym) { + printf("symbol is exported by the kernel\n"); + continue; + } + + vsym = find_symbol_by_name(&symlistv, cur->name); + if (!vsym) + ERROR("couldn't find global function in vmlinux"); + + cur->vm_addr = vsym->sym.st_value; + cur->vm_len = vsym->sym.st_size; + cur->action = LINK; + printf("original symbol at address %016lx (length %d)\n", + cur->vm_addr, cur->vm_len); + } +#endif + + elf_end(elfv.elf); + close(elfv.fd); + + printf("patches_nr = %d\n", patches_nr); + + /* allocate new section data buffers */ + patches_size = sizeof(*patches_data) * patches_nr; + patches_data = malloc(patches_size); + if (!patches_data) + ERROR("malloc"); + memset(patches_data, 0, patches_size); + + relas_size = sizeof(*relas_data) * patches_nr; + relas_data = malloc(relas_size); + if (!relas_data) + ERROR("malloc"); + memset(relas_data, 0, relas_size); + + printf("patches_size = %d\n",patches_size); + printf("relas_size = %d\n",relas_size); + + /* populate new section data buffers */ + i = 0; + for_each_sym(&symlist, cur) { + if (cur->action != PATCH) + continue; + patches_data[i].orig = cur->vm_addr; + patches_data[i].orig_end = cur->vm_addr + cur->vm_len; + relas_data[i].r_offset = i * sizeof(struct kpatch_patch); + relas_data[i].r_info = GELF_R_INFO(cur->index, R_X86_64_64); + } + + /* get next section index from elf header */ + if (!gelf_getehdr(elf.elf, &eh)) + ERROR("gelf_getehdr"); + patches_index = eh.e_shnum; + relas_index = patches_index + 1; + + /* add new section names to shstrtab */ + scn = elf.shstrtab.scn; + shp = &elf.shstrtab.sh; + + data = elf_getdata(scn, NULL); + if (!data) + ERROR("elf_getdata"); + + len = strlen(".patches") + strlen(".rela.patches") + 2; + buf = malloc(data->d_size + len); + memcpy(buf, data->d_buf, data->d_size); + + data->d_buf = buf; + buf = data->d_buf + data->d_size; + + len = strlen(".patches") + 1; + memcpy(buf, ".patches", len); + patches_offset = buf - data->d_buf; + printf("patches_offset = %d\n", patches_offset); + buf += len; + len = strlen(".rela.patches") + 1; + memcpy(buf, ".rela.patches", len); + relas_offset = buf - data->d_buf; + printf("relas_offset = %d\n", relas_offset); + buf += len; + data->d_size = buf - data->d_buf; + + if (!elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY)) + ERROR("elf_flagdata"); + + if (!gelf_update_shdr(scn, shp)) + ERROR("gelf_update_shdr"); + + /* get symtab vars */ + find_section_by_name(&elf, ".symtab", &symtab); + scn = symtab.scn; + shp = &symtab.sh; + + data = elf_getdata(scn, NULL); + if (!data) + ERROR("elf_getdata"); +#if 0 + /* update LINK symbols */ + for_each_sym(&symlist, cur) { + if (cur->action != LINK) + continue; + cur->sym.st_value = cur->vm_addr; + cur->sym.st_info = GELF_ST_INFO(STB_LOCAL,STT_FUNC); + cur->sym.st_shndx = SHN_ABS; + gelf_update_sym(data, cur->index, &cur->sym); + } +#endif + + /* add new section symbols to symtab */ + len = sizeof(GElf_Sym) * 2; + buf = malloc(data->d_size + len); + memcpy(buf, data->d_buf, data->d_size); + + data->d_buf = buf; + buf = data->d_buf + data->d_size; + + memset(&sym, 0, sizeof(GElf_Sym)); + sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION); + + len = sizeof(GElf_Sym); + sym.st_shndx = patches_index; + memcpy(buf, &sym, len); + buf += len; + sym.st_shndx = relas_index; + memcpy(buf, &sym, len); + buf += len; + data->d_size = buf - data->d_buf; + + if (!elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY)) + ERROR("elf_flagdata"); + + if (!gelf_update_shdr(scn, shp)) + ERROR("gelf_update_shdr"); + + /* create .patches section */ + scn = elf_newscn(elf.elf); + if (!scn) + ERROR("elf_newscn"); + + data = elf_newdata(scn); + if (!data) + ERROR("elf_newdata"); + + data->d_size = patches_size; + data->d_buf = patches_data; + data->d_type = ELF_T_BYTE; + + memset(&sh, 0, sizeof(sh)); + sh.sh_type = SHT_PROGBITS; + sh.sh_name = patches_offset; + sh.sh_entsize = sizeof(struct kpatch_patch); + sh.sh_addralign = 8; + sh.sh_flags = SHF_ALLOC; + sh.sh_size = data->d_size; + + if (!gelf_update_shdr(scn, &sh)) + ERROR("gelf_update_shdr"); + + /* create .rela.patches section */ + scn = elf_newscn(elf.elf); + if (!scn) + ERROR("elf_newscn"); + + data = elf_newdata(scn); + if (!data) + ERROR("elf_newdata"); + + data->d_size = relas_size; + data->d_buf = relas_data; + data->d_type = ELF_T_RELA; + + memset(&sh, 0, sizeof(sh)); + sh.sh_type = SHT_RELA; + sh.sh_name = relas_offset; + sh.sh_entsize = sizeof(GElf_Rela); + sh.sh_addralign = 8; + sh.sh_flags = SHF_ALLOC; + sh.sh_link = elf_ndxscn(elf.symtab.scn); + sh.sh_info = patches_index; + sh.sh_size = data->d_size; + + if (!gelf_update_shdr(scn, &sh)) + ERROR("gelf_update_shdr"); + + if (elf_update(elf.elf, ELF_C_WRITE) < 0) + ERROR("elf_update %s", elf_errmsg(-1)); + + elf_end(elf.elf); + close(elf.fd); + + return 0; +} diff --git a/tools/create-diff-object b/tools/create-diff-object new file mode 100755 index 0000000..badcacf Binary files /dev/null and b/tools/create-diff-object differ diff --git a/tools/create-diff-object.c b/tools/create-diff-object.c new file mode 100644 index 0000000..8f780fd --- /dev/null +++ b/tools/create-diff-object.c @@ -0,0 +1,1260 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kpatch-kmod/kpatch.h" + +#define ERROR(format, ...) \ + error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) + +#define DIFF_FATAL(format, ...) \ +({ \ + printf("%s:%d: " format "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + error(2, 0, "unreconcilable difference"); \ +}) + + +/******************* + * Data structures + * ****************/ +struct section; +struct symbol; +struct rela; + +enum status { + NEW, + CHANGED, + SAME, + DEPENDENCY +}; + +struct table { + void *data; + size_t nr; +}; + +struct section { + struct section *twin, *twino; + GElf_Shdr sh; + Elf_Data *data; + char *name; + int index; + enum status status; + int reachable; + union { + struct { /* if (is_rela_section()) */ + struct section *base; + struct table relas; + }; + struct { /* else */ + struct section *rela; + struct symbol *sym; + }; + }; +}; + +struct symbol { + struct symbol *twin, *twino; + struct section *sec; + GElf_Sym sym; + char *name; + int index; + unsigned char bind, type; + enum status status; + int reachable; +}; + +struct rela { + struct rela *twin; + GElf_Rela rela; + struct symbol *sym; + unsigned char type; + int addend; + int offset; + enum status status; +}; + +#define for_each_entry(iter, entry, table, type) \ + for (iter = 0; (iter) < (table)->nr && ((entry) = &((type)(table)->data)[iter]); (iter)++) + +#define for_each_section(iter, entry, table) \ + for_each_entry(iter, entry, table, struct section *) +#define for_each_symbol(iter, entry, table) \ + for_each_entry(iter, entry, table, struct symbol *) +#define for_each_rela(iter, entry, table) \ + for_each_entry(iter, entry, table, struct rela *) + +struct kpatch_elf { + Elf *elf; + struct table sections; + struct table sybmols; +}; + +/******************* + * Helper functions + ******************/ + +char *status_str(enum status status) +{ + switch(status) { + case NEW: + return "NEW"; + case CHANGED: + return "CHANGED"; + case SAME: + return "SAME"; + case DEPENDENCY: + return "DEPENDENCY"; + default: + ERROR("status_str"); + } + /* never reached */ + return NULL; +} + +int is_rela_section(struct section *sec) +{ + return (sec->sh.sh_type == SHT_RELA); +} + +struct section *find_section_by_index(struct table *table, unsigned int index) +{ + struct section *sec; + int i; + + for_each_section(i, sec, table) + if (sec->index == index) + return sec; + + return NULL; +} + +struct section *find_section_by_name(struct table *table, const char *name) +{ + struct section *sec; + int i; + + for_each_section(i, sec, table) + if (!strcmp(sec->name, name)) + return sec; + + return NULL; +} + +struct symbol *find_symbol_by_index(struct table *table, size_t index) +{ + struct symbol *sym; + int i; + + for_each_symbol(i, sym, table) + if (sym->index == index) + return sym; + + return NULL; +} + +struct symbol *find_symbol_by_name(struct table *table, const char *name) +{ + struct symbol *sym; + int i; + + for_each_symbol(i, sym, table) + if (sym->name && !strcmp(sym->name, name)) + return sym; + + return NULL; +} + +void alloc_table(struct table *table, size_t entsize, size_t nr) +{ + size_t size = nr * entsize; + + table->data = malloc(size); + if (!table->data) + ERROR("malloc"); + memset(table->data, 0, size); + table->nr = nr; +} + +/************* + * Functions + * **********/ +void kpatch_create_rela_table(struct kpatch_elf *kelf, struct section *sec) +{ + int rela_nr, i; + struct rela *rela; + unsigned int symndx; + + /* find matching base (text/data) section */ + sec->base = find_section_by_name(&kelf->sections, sec->name + 5); + if (!sec->base) + ERROR("can't find base section for rela section %s", sec->name); + + /* create reverse link from base section to this rela section */ + sec->base->rela = sec; + + /* allocate rela table for section */ + rela_nr = sec->sh.sh_size / sec->sh.sh_entsize; + alloc_table(&sec->relas, sizeof(struct rela), rela_nr); + +#if DEBUG + printf("\n=== rela table for %s (%d entries) ===\n", + sec->base->name, rela_nr); +#endif + /* read and store the rela entries */ + for_each_rela(i, rela, &sec->relas) { + if (!gelf_getrela(sec->data, i, &rela->rela)) + ERROR("gelf_getrela"); + + rela->type = GELF_R_TYPE(rela->rela.r_info); + rela->addend = rela->rela.r_addend; + rela->offset = rela->rela.r_offset; + symndx = GELF_R_SYM(rela->rela.r_info); + rela->sym = find_symbol_by_index(&kelf->sybmols, symndx); + if (!rela->sym) + ERROR("could not find rela entry symbol\n"); + +#if DEBUG + printf("offset %d, type %d, %s %s %d\n", rela->offset, + rela->type, rela->sym->name, + (rela->addend < 0)?"-":"+", abs(rela->addend)); +#endif + } +} + +void kpatch_create_section_table(struct kpatch_elf *kelf) +{ + Elf_Scn *scn = NULL; + struct section *sec; + size_t shstrndx, sections_nr; + int i; + + if (elf_getshdrnum(kelf->elf, §ions_nr)) + ERROR("elf_getshdrnum"); + + /* + * elf_getshdrnum() includes section index 0 but elf_nextscn + * doesn't return that section so subtract one. + */ + sections_nr--; + + alloc_table(&kelf->sections, sizeof(struct section), sections_nr); + + if (elf_getshdrstrndx(kelf->elf, &shstrndx)) + ERROR("elf_getshdrstrndx"); + +#if DEBUG + printf("=== section list (%d) ===\n", sections_nr); +#endif + + for_each_section(i, sec, &kelf->sections) { + scn = elf_nextscn(kelf->elf, scn); + if (!scn) + ERROR("scn NULL"); + + if (!gelf_getshdr(scn, &sec->sh)) + ERROR("gelf_getshdr"); + + sec->name = elf_strptr(kelf->elf, shstrndx, sec->sh.sh_name); + if (!sec->name) + ERROR("elf_strptr"); + + sec->data = elf_getdata(scn, NULL); + if (!sec->data) + ERROR("elf_getdata"); + + sec->index = elf_ndxscn(scn); + +#if DEBUG + printf("ndx %02d, data %08x, size, %08x, name %s\n", + sec->index, sec->data->d_buf, sec->data->d_size, + sec->name); +#endif + } + + /* Sanity check, one more call to elf_nextscn() should return NULL */ + if (elf_nextscn(kelf->elf, scn)) + ERROR("expected NULL"); +} + +void kpatch_create_symbol_table(struct kpatch_elf *kelf) +{ + struct section *symtab; + struct symbol *sym; + int symbols_nr, i; + + symtab = find_section_by_name(&kelf->sections, ".symtab"); + if (!symtab) + ERROR("missing symbol table"); + + symbols_nr = symtab->sh.sh_size / symtab->sh.sh_entsize; + + alloc_table(&kelf->sybmols, sizeof(struct symbol), symbols_nr); + +#if DEBUG + printf("\n=== symbol table (%d entries) ===\n", symbols_nr); +#endif + + /* iterator i declared in for_each_entry() macro */ + for_each_symbol(i, sym, &kelf->sybmols) { + if (i == 0) /* skip symbol 0 */ + continue; + sym->index = i; + + if (!gelf_getsym(symtab->data, i, &sym->sym)) + ERROR("gelf_getsym"); + + sym->name = elf_strptr(kelf->elf, symtab->sh.sh_link, + sym->sym.st_name); + if (!sym->name) + ERROR("elf_strptr"); + + sym->type = GELF_ST_TYPE(sym->sym.st_info); + sym->bind = GELF_ST_BIND(sym->sym.st_info); + + if (sym->sym.st_shndx != SHN_UNDEF && + sym->sym.st_shndx != SHN_ABS) { + sym->sec = find_section_by_index(&kelf->sections, + sym->sym.st_shndx); + if (!sym->sec) + ERROR("couldn't find section for symbol %s\n", + sym->name); + + /* create reverse link from sec to sym */ + sym->sec->sym = sym; + + if (sym->type == STT_SECTION) + /* use the section name as the symbol name */ + sym->name = sym->sec->name; + } +#if 0 + printf("sym %02d, type %d, bind %d, ndx %02d, name %s", + sym->index, sym->type, sym->bind, sym->sym.st_shndx, + sym->name); + if (sym->sec && (sym->type == STT_FUNC || sym->type == STT_OBJECT)) + printf(" -> %s", sym->sec->name); + printf("\n"); +#endif + } + +} + + +struct kpatch_elf *kpatch_elf_open(const char *name) +{ + Elf *elf; + int fd, i; + struct kpatch_elf *kelf; + struct section *sec; + + fd = open(name, O_RDONLY); + if (fd == -1) + ERROR("open"); + + elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!elf) + ERROR("elf_begin"); + + kelf = malloc(sizeof(*kelf)); + if (!kelf) + ERROR("malloc"); + memset(kelf, 0, sizeof(*kelf)); + + /* read and store section, symbol entries from file */ + kelf->elf = elf; + kpatch_create_section_table(kelf); + kpatch_create_symbol_table(kelf); + + /* for each rela section, read and store the rela entries */ + for_each_section(i, sec, &kelf->sections) { + if (!is_rela_section(sec)) + continue; + kpatch_create_rela_table(kelf, sec); + } + + return kelf; +} + +void kpatch_compare_correlated_section(struct section *sec) +{ + struct section *sec1 = sec, *sec2 = sec->twin; + enum status status; + + /* Compare section headers (must match or fatal) */ + if (sec1->sh.sh_type != sec2->sh.sh_type || + sec1->sh.sh_flags != sec2->sh.sh_flags || + sec1->sh.sh_addr != sec2->sh.sh_addr || + sec1->sh.sh_addralign != sec2->sh.sh_addralign || + sec1->sh.sh_entsize != sec2->sh.sh_entsize || + sec1->sh.sh_link != sec1->sh.sh_link) + DIFF_FATAL("%s section header details differ", sec1->name); + + if (sec1->sh.sh_size != sec2->sh.sh_size || + sec1->data->d_size != sec2->data->d_size || + memcmp(sec1->data->d_buf, sec2->data->d_buf, sec1->data->d_size)) + sec1->status = CHANGED; + else + sec1->status = SAME; + + if (!is_rela_section(sec1)) { + /* Sync section symbol status */ + if (sec1->sym) + sec1->sym->status = sec1->status; + /* Sync rela section status */ + if (sec1->rela) + sec1->rela->status = sec1->status; + } + +#if DEBUG + printf("section %s is %s\n", sec1->name, status_str(sec1->status)); +#endif +} + +void kpatch_compare_correlated_sections(struct table *table) +{ + struct section *sec; + int i; + + for_each_section(i, sec, table) + if (sec->twin) + kpatch_compare_correlated_section(sec); + else + sec->status = NEW; +} + +void kpatch_compare_correlated_symbol(struct symbol *sym) +{ + struct symbol *sym1 = sym, *sym2 = sym->twin; + + if (sym1->sym.st_info != sym2->sym.st_info || + sym1->sym.st_other != sym2->sym.st_other || + (sym1->sec && sym2->sec && sym1->sec->twin != sym2->sec) || + (sym1->sec && !sym2->sec) || + (sym2->sec && !sym1->sec)) + DIFF_FATAL("symbol info mismatch: %s", sym1->name); + + if (sym1->type == STT_OBJECT && + sym1->sym.st_size != sym2->sym.st_size) + DIFF_FATAL("object size mismatch: %s", sym1->name); + + if (sym1->sym.st_shndx == SHN_UNDEF || + sym1->sym.st_shndx == SHN_ABS) + sym1->status = SAME; + else if (sym1->sec) + sym1->status = sym1->sec->status; + else if (sym1->status != CHANGED) + sym1->status = SAME; + + /* special case for type FILE */ + if (sym1->type == STT_FILE) + sym1->status = DEPENDENCY; +#if DEBUG + printf("symbol %s is %s\n", sym->name, status_str(sym->status)); +#endif +} + +void kpatch_compare_correlated_symbols(struct table *table) +{ + struct symbol *sym; + int i; + + for_each_symbol(i, sym, table) { + if (i == 0) /* ugh */ + continue; + if (sym->twin) + kpatch_compare_correlated_symbol(sym); + else + sym->status = NEW; + } +} + +void kpatch_compare_correlated_rela(struct rela *rela) +{ + struct rela *rela1 = rela, *rela2 = rela->twin; + + /* + * rela entry status is either SAME or NEW. All correlated entries + * are SAME because the criteria used to correlate them is sufficient + * to consider them unchanged. + */ + rela->status = SAME; +} + +void kpatch_compare_correlated_relas(struct table *table) +{ + struct rela *rela; + int i; + + for_each_rela(i, rela, table) + if (rela->twin) + kpatch_compare_correlated_rela(rela); + else + rela->status = NEW; +} + + +void kpatch_correlate_sections(struct table *table1, struct table *table2) +{ + struct section *sec1, *sec2; + int i, j; + + /* correlate all sections and compare nonrela sections */ + for_each_section(i, sec1, table1) { + for_each_section(j, sec2, table2) { + if (strcmp(sec1->name, sec2->name)) + continue; + sec1->twin = sec2; + sec2->twin = sec1; + break; + } + } +} + +void kpatch_correlate_symbols(struct table *table1, struct table *table2) +{ + struct symbol *sym1, *sym2; + int i, j; + + for_each_symbol(i, sym1, table1) { + if (i == 0) /* ugh */ + continue; + for_each_symbol(j, sym2, table2) { + if (j == 0) /* double ugh */ + continue; + if (!strcmp(sym1->name, sym2->name)) { + sym1->twin = sym2; + sym2->twin = sym1; + break; + } + } + } +} + +void kpatch_correlate_relas(struct section *sec) +{ + struct rela *rela1, *rela2; + int i, j; + + for_each_rela(i, rela1, &sec->relas) { + for_each_rela(j, rela2, &sec->twin->relas) { + if (rela1->type == rela2->type && + rela1->addend == rela2->addend && + !strcmp(rela1->sym->name, rela2->sym->name) && + rela1->offset == rela2->offset) { + rela1->twin = rela2; + rela2->twin = rela1; + break; + } + } + } +} + +void kpatch_compare_elf_headers(Elf *elf1, Elf *elf2) +{ + GElf_Ehdr eh1, eh2; + + if (!gelf_getehdr(elf1, &eh1)) + ERROR("gelf_getehdr"); + + if (!gelf_getehdr(elf2, &eh2)) + ERROR("gelf_getehdr"); + + if (memcmp(eh1.e_ident, eh2.e_ident, EI_NIDENT) || + eh1.e_type != eh2.e_type || + eh1.e_machine != eh2.e_machine || + eh1.e_version != eh2.e_version || + eh1.e_entry != eh2.e_entry || + eh1.e_phoff != eh2.e_phoff || + eh1.e_flags != eh2.e_flags || + eh1.e_ehsize != eh2.e_ehsize || + eh1.e_phentsize != eh2.e_phentsize || + eh1.e_shentsize != eh2.e_shentsize) + DIFF_FATAL("ELF headers differ"); +#if DEBUG + printf("kpatch_compare_elf_headers passed\n"); +#endif +} + +void kpatch_check_program_headers(Elf *elf) +{ + size_t ph_nr; + + if (elf_getphdrnum(elf, &ph_nr)) + ERROR("elf_getphdrnum"); + + if (ph_nr != 0) + DIFF_FATAL("ELF contains program header"); +#if DEBUG + printf("kpatch_check_program_headers passed\n"); +#endif +} + +void kpatch_verify_rela_section_status(struct section *sec) +{ + struct rela *rela; + int i; + + for_each_rela(i, rela, &sec->relas) + if (rela->status == NEW) { + /* + * This rela section really is different. Make + * sure the base section comes along too. + */ + sec->base->status = CHANGED; + return; + } + + /* + * The difference in the section data was due to the renumeration + * of symbol indexes. Consider this rela section unchanged. + */ + sec->status = SAME; +} + +void kpatch_correlate_elfs(struct kpatch_elf *kelf1, struct kpatch_elf *kelf2) +{ + struct section *sec; + int i; + + kpatch_correlate_sections(&kelf1->sections, &kelf2->sections); + kpatch_correlate_symbols(&kelf1->sybmols, &kelf2->sybmols); + + /* at this point, sections are correlated, we can use sec->twin */ + for_each_section(i, sec, &kelf1->sections) + if (is_rela_section(sec)) + kpatch_correlate_relas(sec); +} + +void kpatch_compare_correlated_elements(struct kpatch_elf *kelf) +{ + struct section *sec; + struct rela *rela; + int i, j; + + /* tables are already correlated at this point */ + kpatch_compare_correlated_sections(&kelf->sections); + kpatch_compare_correlated_symbols(&kelf->sybmols); + + for_each_section(i, sec, &kelf->sections) + if (is_rela_section(sec)) + kpatch_compare_correlated_relas(&sec->relas); + + /* + * Check for false positives on changed rela sections + * caused by symbol renumeration. + */ + for_each_section(i, sec, &kelf->sections) + if (is_rela_section(sec) && sec->status == CHANGED) + kpatch_verify_rela_section_status(sec); + + /* + * Find unchanged sections/symbols that are dependencies of + * changed sections + */ + for_each_section(i, sec, &kelf->sections) + if (is_rela_section(sec) && sec->status == CHANGED) + for_each_rela(j, rela, &sec->relas) { +/* + * Nuts, I know. Determine if the section of the symbol referenced by + * the rela entry is associated with a symbol of type STT_SECTION. This + * is to avoid including unchanged local functions or objects that are + * called by a changed function. + */ + if (rela->sym->sym.st_shndx != SHN_UNDEF && + rela->sym->sym.st_shndx != SHN_ABS && + rela->sym->status != CHANGED && + rela->sym->sec->sym->type == STT_SECTION) { + rela->sym->status = DEPENDENCY; + rela->sym->sec->status = DEPENDENCY; + } + +/* + * All symbols referenced by entries in a changed rela section are + * dependencies. + */ + if (rela->sym->status == SAME) + rela->sym->status = DEPENDENCY; + } +} + +void kpatch_dump_kelf(struct kpatch_elf *kelf) +{ + struct section *sec; + struct symbol *sym; + struct rela *rela; + int i, j; + + printf("\n=== Sections ===\n"); + for_each_section(i, sec, &kelf->sections) { + printf("%02d %s (%s)", sec->index, sec->name, status_str(sec->status)); + if (is_rela_section(sec)) { + printf(", base-> %s\n", sec->base->name); + printf("rela section expansion\n"); + for_each_rela(j, rela, &sec->relas) { + printf("sym %d, offset %d, type %d, %s %s %d %s\n", + GELF_R_SYM(rela->rela.r_info), + rela->offset, rela->type, + rela->sym->name, + (rela->addend < 0)?"-":"+", + abs(rela->addend), + status_str(rela->status)); + } + } else { + if (sec->sym) + printf(", sym-> %s", sec->sym->name); + if (sec->rela) + printf(", rela-> %s", sec->rela->name); + } + printf("\n"); + } + + printf("\n=== Symbols ===\n"); + for_each_symbol(i, sym, &kelf->sybmols) { + if (i == 0) /* ugh */ + continue; + printf("sym %02d, type %d=%d, bind %d=%d, ndx %02d, name %s (%s)", + sym->index, sym->type, GELF_ST_TYPE(sym->sym.st_info), sym->bind, GELF_ST_BIND(sym->sym.st_info), sym->sym.st_shndx, + sym->name, status_str(sym->status)); + if (sym->sec && (sym->type == STT_FUNC || sym->type == STT_OBJECT)) + printf(" -> %s", sym->sec->name); + printf("\n"); + } +} + +int kpatch_find_changed_functions(struct kpatch_elf *kelf) +{ + struct symbol *sym; + int i, changed = 0; + + for_each_symbol(i, sym, &kelf->sybmols) { + if (sym->type != STT_FUNC) + continue; + if (sym->status == CHANGED) { + changed = 1; + printf("function %s has changed\n",sym->name); + } + } + + if (!changed) + printf("no changes found\n"); + + return changed; +} + +void kpatch_reachable_symbol(struct symbol *sym) +{ + struct rela *rela; + struct section *sec; + int i; + + sym->reachable = 1; +#if DEBUG + printf("symbol %s is reachable\n", sym->name); +#endif + if (!sym->sec) + return; + sec = sym->sec; + sec->reachable = 1; +#if DEBUG + printf("section %s is reachable\n", sec->name); +#endif + if (sec->sym) + sec->sym->reachable = 1; +#if DEBUG + printf("symbol %s is reachable\n", sym->sec->name); +#endif + if (!sec->rela) + return; + sec->rela->reachable = 1; +#if DEBUG + printf("section %s is reachable\n", sec->rela->name); +#endif + for_each_rela(i, rela, &sec->rela->relas) { + if (rela->sym->status == SAME || + rela->sym->reachable) + continue; + kpatch_reachable_symbol(rela->sym); + } +} + +void kpatch_validate_reachability(struct kpatch_elf *kelf) +{ + struct symbol *sym; + struct section *sec; + int i; + + for_each_symbol(i, sym, &kelf->sybmols) + if (!sym->reachable && sym->status != SAME && + sym->type == STT_FUNC) + kpatch_reachable_symbol(sym); + + for_each_section(i, sec, &kelf->sections) + if (sec->status != SAME && !sec->reachable && + strcmp(sec->name, ".symtab") && + strcmp(sec->name, ".strtab")) + DIFF_FATAL("unreachable changed section %s", + sec->name); + + printf("All changed sections are reachable\n"); +} + +void kpatch_generate_output(struct kpatch_elf *kelf, struct kpatch_elf **kelfout) +{ + int sections_nr = 0, symbols_nr = 0, i, index; + struct section *sec, *secout; + struct symbol *sym, *symout; + struct kpatch_elf *out; + + /* count output sections */ + for_each_section(i, sec, &kelf->sections) { + /* include these sections even if they haven't changed */ + if (sec->status == SAME && + (!strcmp(sec->name, ".shstrtab") || + !strcmp(sec->name, ".strtab") || + !strcmp(sec->name, ".symtab"))) + sec->status = DEPENDENCY; + + if (sec->status != SAME) + sections_nr++; + } + +#if DEBUG + printf("outputting %d sections\n",sections_nr); +#endif + + /* count output symbols */ + for_each_symbol(i, sym, &kelf->sybmols) { + if (i == 0 || sym->status != SAME) + symbols_nr++; + } +#if DEBUG + printf("outputting %d symbols\n",symbols_nr); +#endif + + /* allocate output kelf */ + out = malloc(sizeof(*out)); + if (!out) + ERROR("malloc"); + memset(out, 0, sizeof(*out)); + + /* allocate tables */ + alloc_table(&out->sections, sizeof(struct section), sections_nr); + alloc_table(&out->sybmols, sizeof(struct symbol), symbols_nr); + + /* copy to output kelf sections, link to kelf, and reindex */ + index = 0; + for_each_section(i, sec, &kelf->sections) { + if (sec->status == SAME) + continue; + + secout = &((struct section *)(out->sections.data))[index]; + *secout = *sec; + secout->index = ++index; + secout->twino = sec; + sec->twino = secout; + } + + /* copy to output kelf symbols, link to kelf, and reindex */ + index = 0; + for_each_symbol(i, sym, &kelf->sybmols) { + if (i != 0 && sym->status == SAME) + continue; + + symout = &((struct symbol *)(out->sybmols.data))[index]; + *symout = *sym; + symout->index = index; + symout->twino = sym; + sym->twino = symout; + index++; + + if (i == 0) + continue; + + if (sym->sec && sym->sec->twino) + symout->sym.st_shndx = sym->sec->twino->index; + } + + for_each_symbol(i, sym, &out->sybmols) { + if (i == 0) + continue; + /* + * Search symbol table for local functions whose sections are + * not included, and modify them to be non-local. + */ + if ((sym->type == STT_OBJECT || + sym->type == STT_FUNC) && + sym->status == DEPENDENCY) { + sym->type = STT_NOTYPE; + sym->bind = STB_GLOBAL; + sym->sym.st_info = GELF_ST_INFO(STB_GLOBAL, STT_NOTYPE); + sym->sym.st_shndx = SHN_UNDEF; + sym->sym.st_size = 0; + } + } + + *kelfout = out; +} + +void kpatch_create_rela_section(struct section *sec, int link) +{ + struct rela *rela; + int i, symndx, type; + char *buf; + size_t size; + + /* create new rela data buffer */ + size = sec->sh.sh_size; + buf = malloc(size); + if (!buf) + ERROR("malloc"); + memset(buf, 0, size); + + /* reindex and copy into buffer */ + for_each_rela(i, rela, &sec->relas) { +#if DEBUG + if (!rela->sym || !rela->sym->twino) + ERROR("expected rela symbol"); +#endif + symndx = rela->sym->twino->index; + type = GELF_R_TYPE(rela->rela.r_info); + rela->rela.r_info = GELF_R_INFO(symndx, type); + + memcpy(buf + (i * sec->sh.sh_entsize), &rela->rela, + sec->sh.sh_entsize); + } + + sec->data->d_buf = buf; + /* size is unchanged */ + + sec->sh.sh_link = link; + /* info is section index of text section that matches this rela */ + sec->sh.sh_info = sec->twino->base->twino->index; +} + +void kpatch_create_rela_sections(struct kpatch_elf *kelf) +{ + struct section *sec; + int i, link; + + link = find_section_by_name(&kelf->sections, ".symtab")->index; + + /* reindex rela symbols */ + for_each_section(i, sec, &kelf->sections) + if (is_rela_section(sec)) + kpatch_create_rela_section(sec, link); +} + +#if DEBUG +void print_strtab(char *buf, size_t size) +{ + int i; + + for (i = 0; i < size; i++) { + if (buf[i] == 0) + printf("\\0"); + else + printf("%c",buf[i]); + } +} +#endif + +void kpatch_create_shstrtab(struct kpatch_elf *kelf) +{ + struct section *shstrtab, *sec; + size_t size, offset, len; + int i; + char *buf; + + shstrtab = find_section_by_name(&kelf->sections, ".shstrtab"); + if (!shstrtab) + ERROR("find_section_by_name"); + + /* determine size of string table */ + size = 1; /* for initial NULL terminator */ + for_each_section(i, sec, &kelf->sections) + size += strlen(sec->name) + 1; /* include NULL terminator */ + + /* allocate data buffer */ + buf = malloc(size); + if (!buf) + ERROR("malloc"); + memset(buf, 0, size); + + /* populate string table and link with section header */ + offset = 1; + for_each_section(i, sec, &kelf->sections) { + len = strlen(sec->name) + 1; + sec->sh.sh_name = offset; + memcpy(buf + offset, sec->name, len); + offset += len; + } + +#if DEBUG + if (offset != size) + ERROR("shstrtab size mismatch"); +#endif + + shstrtab->data->d_buf = buf; + shstrtab->data->d_size = size; + +#if DEBUG + printf("shstrtab: "); + print_strtab(buf, size); + printf("\n"); + + for_each_section(i, sec, &kelf->sections) + printf("%s @ shstrtab offset %d\n",sec->name,sec->sh.sh_name); +#endif +} + +void kpatch_create_strtab(struct kpatch_elf *kelf) +{ + struct section *strtab; + struct symbol *sym; + size_t size, offset, len; + int i; + char *buf; + + strtab = find_section_by_name(&kelf->sections, ".strtab"); + if (!strtab) + ERROR("find_section_by_name"); + + /* determine size of string table */ + size = 1; /* for initial NULL terminator */ + for_each_symbol(i, sym, &kelf->sybmols) { + if (i == 0 || sym->type == STT_SECTION) + continue; + size += strlen(sym->name) + 1; /* include NULL terminator */ + } + + /* allocate data buffer */ + buf = malloc(size); + if (!buf) + ERROR("malloc"); + memset(buf, 0, size); + + /* populate string table and link with section header */ + offset = 1; + for_each_symbol(i, sym, &kelf->sybmols) { + if (i == 0) + continue; + if (sym->type == STT_SECTION) { + sym->sym.st_name = 0; + continue; + } + len = strlen(sym->name) + 1; + sym->sym.st_name = offset; + memcpy(buf + offset, sym->name, len); + offset += len; + } + +#if DEBUG + if (offset != size) + ERROR("shstrtab size mismatch"); +#endif + + strtab->data->d_buf = buf; + strtab->data->d_size = size; + +#if DEBUG + printf("strtab: "); + print_strtab(buf, size); + printf("\n"); + + for_each_symbol(i, sym, &kelf->sybmols) + printf("%s @ strtab offset %d\n",sym->name,sym->sym.st_name); +#endif +} + +void kpatch_create_symtab(struct kpatch_elf *kelf) +{ + struct section *symtab; + struct symbol *sym; + char *buf; + size_t size; + int i; + + symtab = find_section_by_name(&kelf->sections, ".symtab"); + if (!symtab) + ERROR("find_section_by_name"); + + /* create new symtab buffer */ + size = kelf->sybmols.nr * symtab->sh.sh_entsize; + buf = malloc(size); + if (!buf) + ERROR("malloc"); + memset(buf, 0, size); + + for_each_symbol(i, sym, &kelf->sybmols) { + memcpy(buf + (i * symtab->sh.sh_entsize), &sym->sym, + symtab->sh.sh_entsize); + } + + symtab->data->d_buf = buf; + symtab->data->d_size = size; + + symtab->sh.sh_link = + find_section_by_name(&kelf->sections, ".strtab")->index; + symtab->sh.sh_info = + find_section_by_name(&kelf->sections, ".shstrtab")->index; +} + +#if 0 +void kpatch_link_symtab_vmlinux(struct kpatch_elf *kelf, struct kpatch_elf *vmkelf) +{ + struct symbol *sym, *vmsym; +#define BUFSIZE 255 + char kstrbuf[BUFSIZE]; + int i; + + for_each_symbol(i, sym, &kelf->sybmols) { + if (GELF_ST_BIND(sym->sym.st_info) != STB_GLOBAL) + continue; + + /* figure out if symbol is exported by the kernel */ + snprintf(kstrbuf, BUFSIZE, "%s%s", "__ksymtab_", sym->name); + printf("looking for %s\n",kstrbuf); + vmsym = find_symbol_by_name(&vmkelf->sybmols, kstrbuf); + if (vmsym) + continue; + + /* it is not, lookup address in vmlinux */ + vmsym = find_symbol_by_name(&vmkelf->sybmols, sym->name); + if (!vmsym) + ERROR("symbol not found in vmlinux"); + + sym->sym.st_value = vmsym->sym.st_value; + sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, + GELF_ST_TYPE(vmsym->sym.st_info)); + sym->sym.st_shndx = SHN_ABS; +#if DEBUG + printf("symbol %s found with address %016lx\n", + sym->name, sym->sym.st_value); +#endif + } +} +#endif + +void kpatch_write_output_elf(struct kpatch_elf *kelf, Elf *elf) +{ + int fd, i, index = 0; + struct section *sec; + Elf *elfout; + GElf_Ehdr eh, ehout; + Elf_Scn *scn; + Elf_Data *data; + GElf_Shdr sh; + + /* TODO make this argv */ + fd = creat("output.kpatch_gen", 0777); + if (fd == -1) + ERROR("creat"); + + elfout = elf_begin(fd, ELF_C_WRITE, NULL); + if (!elfout) + ERROR("elf_begin"); + + if (!gelf_newehdr(elfout, gelf_getclass(kelf->elf))) + ERROR("gelf_newehdr"); + + if (!gelf_getehdr(elfout, &ehout)) + ERROR("gelf_getehdr"); + + if (!gelf_getehdr(elf, &eh)) + ERROR("gelf_getehdr"); + + memset(&ehout, 0, sizeof(ehout)); + ehout.e_ident[EI_DATA] = eh.e_ident[EI_DATA]; + ehout.e_machine = eh.e_machine; + ehout.e_type = eh.e_type; + ehout.e_version = EV_CURRENT; + ehout.e_shstrndx = find_section_by_name(&kelf->sections, ".shstrtab")->index; + + /* add changed sections */ + for_each_section(i, sec, &kelf->sections) { + scn = elf_newscn(elfout); + if (!scn) + ERROR("elf_newscn"); + + data = elf_newdata(scn); + if (!data) + ERROR("elf_newdata"); + + *data = *sec->data; + + if(!gelf_getshdr(scn, &sh)) + ERROR("gelf_getshdr"); + + sh = sec->sh; + + if (!elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY)) + ERROR("elf_flagdata"); + + if (!gelf_update_shdr(scn, &sh)) + ERROR("gelf_update_shdr"); + } + + if (!gelf_update_ehdr(elfout, &ehout)) + ERROR("gelf_update_ehdr"); + + if (elf_update(elfout, ELF_C_WRITE) < 0) { + printf("%s\n",elf_errmsg(-1)); + ERROR("elf_update"); + } +} + +int main(int argc, char *argv[]) +{ + struct kpatch_elf *kelf_base, *kelf_patched, *kelf_out, *kelf_vmlinux; + + elf_version(EV_CURRENT); + + kelf_base = kpatch_elf_open(argv[1]); + kelf_patched = kpatch_elf_open(argv[2]); + kelf_vmlinux = kpatch_elf_open("/home/sjennings/projects/obj/vmlinux"); + + kpatch_compare_elf_headers(kelf_base->elf, kelf_patched->elf); + kpatch_check_program_headers(kelf_base->elf); + kpatch_check_program_headers(kelf_patched->elf); + + kpatch_correlate_elfs(kelf_base, kelf_patched); + /* + * After this point, we don't care about kelf_base anymore. + * We access its sections via the twin pointers in the + * section, symbol, and rela lists of kelf_patched. + */ + kpatch_compare_correlated_elements(kelf_patched); +#if DEBUG + kpatch_dump_kelf(kelf_patched); +#endif + /* + * At this point, the kelf is fully linked and statuses on + * all sections and symbols have been set. + */ + + /* Go through changes and make sure they are hot-patchable */ + kpatch_validate_reachability(kelf_patched); + + if (!kpatch_find_changed_functions(kelf_patched)) + return 0; + + /* Generate the output elf */ + kpatch_generate_output(kelf_patched, &kelf_out); + /*kpatch_link_symtab_vmlinux(kelf_out, kelf_vmlinux);*/ + kpatch_create_rela_sections(kelf_out); + kpatch_create_shstrtab(kelf_out); + kpatch_create_strtab(kelf_out); + kpatch_create_symtab(kelf_out); +#if DEBUG + kpatch_dump_kelf(kelf_out); +#endif + + kpatch_write_output_elf(kelf_out, kelf_patched->elf); + + return 0; +} diff --git a/tools/link-vmlinux-syms b/tools/link-vmlinux-syms new file mode 100755 index 0000000..0c371ea Binary files /dev/null and b/tools/link-vmlinux-syms differ diff --git a/tools/link-vmlinux-syms.c b/tools/link-vmlinux-syms.c new file mode 100644 index 0000000..818c75e --- /dev/null +++ b/tools/link-vmlinux-syms.c @@ -0,0 +1,431 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kpatch-kmod/kpatch.h" + +#define ERROR(format, ...) \ + error(1, 0, "%s: %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__) + +struct section { + Elf_Scn *scn; + GElf_Shdr sh; +}; + +enum symaction { + NOOP, /* do nothing, default */ + PATCH, /* sym is a patched function */ + LINK, /* sym is a non-exported global sym */ +}; + +struct sym { + struct sym *next; + GElf_Sym sym; + char *name; + int index; + enum symaction action; + unsigned long vm_addr; + size_t vm_len; +}; + +struct symlist { + struct sym *head; + size_t len; +}; + +struct elf { + Elf *elf; + int fd; + size_t shstrndx; + struct section symtab, shstrtab; +}; + +#define for_each_sym(list, iter) \ + for((iter) = (list)->head; (iter); (iter) = (iter)->next) + +enum elfmode { + RDONLY, + RDWR +}; + +static void open_elf(char *path, enum elfmode elfmode, struct elf *elf) +{ + mode_t mode; + Elf_Cmd cmd; + + switch(elfmode) { + case RDONLY: + mode = O_RDONLY; + cmd = ELF_C_READ_MMAP; + break; + case RDWR: + mode = O_RDWR; + cmd = ELF_C_RDWR; + break; + } + + if ((elf->fd = open(path, mode, 0)) < 0) + ERROR("open"); + + elf->elf = elf_begin(elf->fd, cmd, NULL); + if (!elf->elf) { + printf("%s\n", elf_errmsg(-1)); + ERROR("elf_begin"); + } + + if (elf_getshdrstrndx(elf->elf, &elf->shstrndx)) + ERROR("elf_getshdrstrndx"); +} + +static void insert_sym(struct symlist *list, GElf_Sym *sym, char *name, + int index) +{ + struct sym *newsym; + + newsym = malloc(sizeof(*newsym)); + if (!newsym) + ERROR("malloc"); + memset(newsym, 0, sizeof(*newsym)); + newsym->sym = *sym; + newsym->name = name; + newsym->index = index; + + newsym->next = list->head; + list->head = newsym; +} + +static void find_section_by_name(struct elf *elf, char *name, struct section *sec) +{ + Elf_Scn *scn = NULL; + GElf_Shdr sh; + char *secname; + + while (scn = elf_nextscn(elf->elf, scn)) { + if (!gelf_getshdr(scn, &sh)) + ERROR("gelf_getshdr"); + + secname = elf_strptr(elf->elf, elf->shstrndx, sh.sh_name); + if (!secname) + ERROR("elf_strptr scn"); + + if (!strcmp(secname, name)) + break; + } + + if (!scn) + ERROR("no section %s found", name); + + sec->scn = scn; + sec->sh = sh; +} + +static void create_symlist(struct elf *elf, struct symlist *symlist) +{ + Elf_Scn *scn = elf->symtab.scn; + GElf_Shdr *sh = &elf->symtab.sh; + GElf_Sym sym; + Elf_Data *data; + char *name; + int i; + + /* get symtab data buffer */ + data = elf_getdata(scn, NULL); + if (!data) + ERROR("elf_getdata"); + + /* find (local) function symbols + * NOTE: If the function symbol is in the kpatch-gen file, it needs + * to be patched. If the function didn't need to be patched, + * it wouldn't have been incldued in the kpatch-gen file. + */ + symlist->len = sh->sh_size / sh->sh_entsize; + for (i = 0; i < symlist->len; i++) { + if (!gelf_getsym(data, i, &sym)) + ERROR("gelf_getsym"); + + name = elf_strptr(elf->elf, sh->sh_link, sym.st_name); + if(!name) + ERROR("elf_strptr sym"); + + insert_sym(symlist, &sym, name, i); + } +} + +static struct sym *find_symbol_by_name(struct symlist *list, char *name) +{ + struct sym *cur; + + for_each_sym(list, cur) + if (!strcmp(cur->name, name)) + return cur; + return NULL; +} + +int main(int argc, char **argv) +{ + struct symlist symlist, symlistv; + struct sym *cur, *vsym; + struct elf elf, elfv; + char name[255]; + void *buf; + struct kpatch_patch *patches_data; + GElf_Rela *relas_data; + int patches_nr = 0, i, patches_size, relas_size, len; + int patches_offset, relas_offset, patches_index, relas_index; + struct section symtab; + Elf_Scn *scn; + Elf_Data *data; + GElf_Shdr sh, *shp; + GElf_Ehdr eh; + GElf_Sym sym; + + /* set elf version (required by libelf) */ + if (elf_version(EV_CURRENT) == EV_NONE) + ERROR("elf_version"); + + memset(&elf, 0, sizeof(elf)); + memset(&elfv, 0, sizeof(elfv)); + open_elf(argv[1], RDWR, &elf); + open_elf(argv[2], RDONLY, &elfv); + + find_section_by_name(&elf, ".symtab", &(elf.symtab)); + find_section_by_name(&elfv, ".symtab", &(elfv.symtab)); + + find_section_by_name(&elf, ".shstrtab", &(elf.shstrtab)); + + memset(&symlist, 0, sizeof(symlist)); + memset(&symlistv, 0, sizeof(symlistv)); + create_symlist(&elf, &symlist); + create_symlist(&elfv, &symlistv); + +#if 0 + /* lookup patched functions in vmlinux */ + for_each_sym(&symlist, cur) { + if (GELF_ST_TYPE(cur->sym.st_info) != STT_FUNC) + continue; + + printf("found patched function %s\n", cur->name); + + vsym = find_symbol_by_name(&symlistv, cur->name); + if (!vsym) + ERROR("couldn't find patched function in vmlinux"); + cur->vm_addr = vsym->sym.st_value; + cur->vm_len = vsym->sym.st_size; + cur->action = PATCH; + printf("original function at address %016lx (length %d)\n", + cur->vm_addr, cur->vm_len); + patches_nr++; + } +#endif + + /* lookup non-exported globals and insert vmlinux address */ + for_each_sym(&symlist, cur) { + if (GELF_ST_TYPE(cur->sym.st_info) != STT_NOTYPE || + GELF_ST_BIND(cur->sym.st_info) != STB_GLOBAL || + cur->sym.st_shndx != STN_UNDEF || + !strcmp(cur->name, "kpatch_register") || + !strcmp(cur->name, "kpatch_unregister")) + continue; + + printf("found global symbol %s\n", cur->name); + sprintf(name, "__kstrtab_%s", cur->name); + vsym = find_symbol_by_name(&symlistv, name); + if (vsym) { + printf("symbol is exported by the kernel\n"); + continue; + } + + vsym = find_symbol_by_name(&symlistv, cur->name); + if (!vsym) + ERROR("couldn't find global function in vmlinux"); + + cur->vm_addr = vsym->sym.st_value; + cur->vm_len = vsym->sym.st_size; + cur->action = LINK; + printf("original symbol at address %016lx (length %d)\n", + cur->vm_addr, cur->vm_len); + } + + elf_end(elfv.elf); + close(elfv.fd); + +#if 0 + printf("patches_nr = %d\n", patches_nr); + + /* allocate new section data buffers */ + patches_size = sizeof(*patches_data) * patches_nr; + patches_data = malloc(patches_size); + if (!patches_data) + ERROR("malloc"); + memset(patches_data, 0, patches_size); + + relas_size = sizeof(*relas_data) * patches_nr; + relas_data = malloc(relas_size); + if (!relas_data) + ERROR("malloc"); + memset(relas_data, 0, relas_size); + + printf("patches_size = %d\n",patches_size); + printf("relas_size = %d\n",relas_size); + + /* populate new section data buffers */ + i = 0; + for_each_sym(&symlist, cur) { + if (cur->action != PATCH) + continue; + patches_data[i].orig = cur->vm_addr; + patches_data[i].orig_end = cur->vm_addr + cur->vm_len; + relas_data[i].r_offset = i * sizeof(struct kpatch_patch); + relas_data[i].r_info = GELF_R_INFO(cur->index, R_X86_64_64); + } + + /* get next section index from elf header */ + if (!gelf_getehdr(elf.elf, &eh)) + ERROR("gelf_getehdr"); + patches_index = eh.e_shnum; + relas_index = patches_index + 1; + + /* add new section names to shstrtab */ + scn = elf.shstrtab.scn; + shp = &elf.shstrtab.sh; + + data = elf_getdata(scn, NULL); + if (!data) + ERROR("elf_getdata"); + + len = strlen(".patches") + strlen(".rela.patches") + 2; + buf = malloc(data->d_size + len); + memcpy(buf, data->d_buf, data->d_size); + + data->d_buf = buf; + buf = data->d_buf + data->d_size; + + len = strlen(".patches") + 1; + memcpy(buf, ".patches", len); + patches_offset = buf - data->d_buf; + printf("patches_offset = %d\n", patches_offset); + buf += len; + len = strlen(".rela.patches") + 1; + memcpy(buf, ".rela.patches", len); + relas_offset = buf - data->d_buf; + printf("relas_offset = %d\n", relas_offset); + buf += len; + data->d_size = buf - data->d_buf; + + if (!elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY)) + ERROR("elf_flagdata"); + + if (!gelf_update_shdr(scn, shp)) + ERROR("gelf_update_shdr"); + + /* get symtab vars */ +#endif + find_section_by_name(&elf, ".symtab", &symtab); + scn = symtab.scn; + shp = &symtab.sh; + + data = elf_getdata(scn, NULL); + if (!data) + ERROR("elf_getdata"); + /* update LINK symbols */ + for_each_sym(&symlist, cur) { + if (cur->action != LINK) + continue; + cur->sym.st_value = cur->vm_addr; + cur->sym.st_info = GELF_ST_INFO(STB_LOCAL,STT_FUNC); + cur->sym.st_shndx = SHN_ABS; + gelf_update_sym(data, cur->index, &cur->sym); + } + +#if 0 + /* add new section symbols to symtab */ + len = sizeof(GElf_Sym) * 2; + buf = malloc(data->d_size + len); + memcpy(buf, data->d_buf, data->d_size); + + data->d_buf = buf; + buf = data->d_buf + data->d_size; + + memset(&sym, 0, sizeof(GElf_Sym)); + sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION); + + len = sizeof(GElf_Sym); + sym.st_shndx = patches_index; + memcpy(buf, &sym, len); + buf += len; + sym.st_shndx = relas_index; + memcpy(buf, &sym, len); + buf += len; + data->d_size = buf - data->d_buf; + + if (!elf_flagdata(data, ELF_C_SET, ELF_F_DIRTY)) + ERROR("elf_flagdata"); + + if (!gelf_update_shdr(scn, shp)) + ERROR("gelf_update_shdr"); + + /* create .patches section */ + scn = elf_newscn(elf.elf); + if (!scn) + ERROR("elf_newscn"); + + data = elf_newdata(scn); + if (!data) + ERROR("elf_newdata"); + + data->d_size = patches_size; + data->d_buf = patches_data; + data->d_type = ELF_T_BYTE; + + memset(&sh, 0, sizeof(sh)); + sh.sh_type = SHT_PROGBITS; + sh.sh_name = patches_offset; + sh.sh_entsize = sizeof(struct kpatch_patch); + sh.sh_addralign = 8; + sh.sh_flags = SHF_ALLOC; + sh.sh_size = data->d_size; + + if (!gelf_update_shdr(scn, &sh)) + ERROR("gelf_update_shdr"); + + /* create .rela.patches section */ + scn = elf_newscn(elf.elf); + if (!scn) + ERROR("elf_newscn"); + + data = elf_newdata(scn); + if (!data) + ERROR("elf_newdata"); + + data->d_size = relas_size; + data->d_buf = relas_data; + data->d_type = ELF_T_RELA; + + memset(&sh, 0, sizeof(sh)); + sh.sh_type = SHT_RELA; + sh.sh_name = relas_offset; + sh.sh_entsize = sizeof(GElf_Rela); + sh.sh_addralign = 8; + sh.sh_flags = SHF_ALLOC; + sh.sh_link = elf_ndxscn(elf.symtab.scn); + sh.sh_info = patches_index; + sh.sh_size = data->d_size; + + if (!gelf_update_shdr(scn, &sh)) + ERROR("gelf_update_shdr"); + +#endif + if (elf_update(elf.elf, ELF_C_WRITE) < 0) + ERROR("elf_update %s", elf_errmsg(-1)); + + elf_end(elf.elf); + close(elf.fd); + + return 0; +}