mirror of https://github.com/dynup/kpatch
454 lines
10 KiB
C
454 lines
10 KiB
C
/*
|
|
* add-patches-section.c
|
|
*
|
|
* Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
/*
|
|
* This tool takes an elf object, the output of create-diff-object
|
|
* and the base vmlinux as arguments and adds two new sections
|
|
* to the elf object; .patches and .rela.patches.
|
|
*
|
|
* These two sections allow the kpatch core modules to know which
|
|
* functions are overridden by the patch module.
|
|
*
|
|
* For each struct kpatch_patch entry in the .patches section, the core
|
|
* module will register as an ftrace handler for the old function. The new
|
|
* function will return to the caller of the old function, not the old function
|
|
* itself, bypassing the old function.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <error.h>
|
|
#include <gelf.h>
|
|
#include <unistd.h>
|
|
|
|
#include "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, *tail;
|
|
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;
|
|
|
|
if (list->tail)
|
|
list->tail->next = newsym;
|
|
if (!list->head)
|
|
list->head = newsym;
|
|
list->tail = 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");
|
|
|
|
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, struct sym *sym,
|
|
char *hint)
|
|
{
|
|
struct sym *cur, *ret = NULL;
|
|
char *name = sym->name, *curfile = NULL;
|
|
|
|
/* try to find a local symbol in the hint file first */
|
|
if (hint && GELF_ST_BIND(sym->sym.st_info) == STB_LOCAL) {
|
|
for_each_sym(list, cur) {
|
|
if (GELF_ST_TYPE(cur->sym.st_info) == STT_FILE)
|
|
curfile = cur->name;
|
|
if (!curfile || strcmp(curfile, hint))
|
|
continue;
|
|
if (!strcmp(cur->name, name)) {
|
|
if (ret)
|
|
ERROR("unresolvable symbol ambiguity for symbol '%s' in file '%s'", name, hint);
|
|
ret = cur;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* search globally for the symbol */
|
|
for_each_sym(list, cur)
|
|
if (GELF_ST_BIND(sym->sym.st_info) == STB_GLOBAL &&
|
|
!strcmp(cur->name, name))
|
|
return cur;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* TODO: de-dup common code above these point with code
|
|
* in link-vmlinux-syms.c
|
|
*/
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct symlist symlist, symlistv;
|
|
struct sym *cur, *vsym;
|
|
struct elf elf, elfv;
|
|
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;
|
|
char *hint = NULL;
|
|
|
|
/* 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_FILE)
|
|
hint = cur->name;
|
|
|
|
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, hint);
|
|
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 %zu)\n",
|
|
cur->vm_addr, cur->vm_len);
|
|
patches_nr++;
|
|
}
|
|
|
|
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].old_addr = cur->vm_addr;
|
|
patches_data[i].old_size = 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);
|
|
i++;
|
|
}
|
|
|
|
/* 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");
|
|
|
|
/* 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;
|
|
}
|