kpatch/kpatch-build/add-patches-section.c
Josh Poimboeuf 5e25365244 Revert #186 (add dynamic symbol linking support)
We merged PR #186 a little too hastily.  It seg faults with the new
parainstructions-section.patch in the integration test suite.  Reverting
it for now until we get it figured out.

This reverts commit e1177e3a03.
This reverts commit 880e271841.
This reverts commit 2de5f6cbfb.
This reverts commit 38b7ac74ad.
This reverts commit 108cd9f95e.
2014-05-15 17:34:16 -05:00

465 lines
11 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);
if (!patches_nr)
ERROR("no patched functions");
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;
patches_data[i].new_size = cur->sym.st_size;
/*
* Use a relocation to set patches_data[i].new_addr at module
* loading time.
*/
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;
}