linux/tools/objtool/special.c
<<
>>
Prefs
   1/*
   2 * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
   3 *
   4 * This program is free software; you can redistribute it and/or
   5 * modify it under the terms of the GNU General Public License
   6 * as published by the Free Software Foundation; either version 2
   7 * of the License, or (at your option) any later version.
   8 *
   9 * This program is distributed in the hope that it will be useful,
  10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12 * GNU General Public License for more details.
  13 *
  14 * You should have received a copy of the GNU General Public License
  15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
  16 */
  17
  18/*
  19 * This file reads all the special sections which have alternate instructions
  20 * which can be patched in or redirected to at runtime.
  21 */
  22
  23#include <stdlib.h>
  24#include <string.h>
  25
  26#include "special.h"
  27#include "warn.h"
  28
  29#define EX_ENTRY_SIZE           12
  30#define EX_ORIG_OFFSET          0
  31#define EX_NEW_OFFSET           4
  32
  33#define JUMP_ENTRY_SIZE         24
  34#define JUMP_ORIG_OFFSET        0
  35#define JUMP_NEW_OFFSET         8
  36
  37#define ALT_ENTRY_SIZE          13
  38#define ALT_ORIG_OFFSET         0
  39#define ALT_NEW_OFFSET          4
  40#define ALT_FEATURE_OFFSET      8
  41#define ALT_ORIG_LEN_OFFSET     10
  42#define ALT_NEW_LEN_OFFSET      11
  43
  44#define X86_FEATURE_POPCNT (4*32+23)
  45
  46struct special_entry {
  47        const char *sec;
  48        bool group, jump_or_nop;
  49        unsigned char size, orig, new;
  50        unsigned char orig_len, new_len; /* group only */
  51        unsigned char feature; /* ALTERNATIVE macro CPU feature */
  52};
  53
  54struct special_entry entries[] = {
  55        {
  56                .sec = ".altinstructions",
  57                .group = true,
  58                .size = ALT_ENTRY_SIZE,
  59                .orig = ALT_ORIG_OFFSET,
  60                .orig_len = ALT_ORIG_LEN_OFFSET,
  61                .new = ALT_NEW_OFFSET,
  62                .new_len = ALT_NEW_LEN_OFFSET,
  63                .feature = ALT_FEATURE_OFFSET,
  64        },
  65        {
  66                .sec = "__jump_table",
  67                .jump_or_nop = true,
  68                .size = JUMP_ENTRY_SIZE,
  69                .orig = JUMP_ORIG_OFFSET,
  70                .new = JUMP_NEW_OFFSET,
  71        },
  72        {
  73                .sec = "__ex_table",
  74                .size = EX_ENTRY_SIZE,
  75                .orig = EX_ORIG_OFFSET,
  76                .new = EX_NEW_OFFSET,
  77        },
  78        {},
  79};
  80
  81static int get_alt_entry(struct elf *elf, struct special_entry *entry,
  82                         struct section *sec, int idx,
  83                         struct special_alt *alt)
  84{
  85        struct rela *orig_rela, *new_rela;
  86        unsigned long offset;
  87
  88        offset = idx * entry->size;
  89
  90        alt->group = entry->group;
  91        alt->jump_or_nop = entry->jump_or_nop;
  92
  93        if (alt->group) {
  94                alt->orig_len = *(unsigned char *)(sec->data + offset +
  95                                                   entry->orig_len);
  96                alt->new_len = *(unsigned char *)(sec->data + offset +
  97                                                  entry->new_len);
  98        }
  99
 100        if (entry->feature) {
 101                unsigned short feature;
 102
 103                feature = *(unsigned short *)(sec->data + offset +
 104                                              entry->feature);
 105
 106                /*
 107                 * It has been requested that we don't validate the !POPCNT
 108                 * feature path which is a "very very small percentage of
 109                 * machines".
 110                 */
 111                if (feature == X86_FEATURE_POPCNT)
 112                        alt->skip_orig = true;
 113        }
 114
 115        orig_rela = find_rela_by_dest(sec, offset + entry->orig);
 116        if (!orig_rela) {
 117                WARN_FUNC("can't find orig rela", sec, offset + entry->orig);
 118                return -1;
 119        }
 120        if (orig_rela->sym->type != STT_SECTION) {
 121                WARN_FUNC("don't know how to handle non-section rela symbol %s",
 122                           sec, offset + entry->orig, orig_rela->sym->name);
 123                return -1;
 124        }
 125
 126        alt->orig_sec = orig_rela->sym->sec;
 127        alt->orig_off = orig_rela->addend;
 128
 129        if (!entry->group || alt->new_len) {
 130                new_rela = find_rela_by_dest(sec, offset + entry->new);
 131                if (!new_rela) {
 132                        WARN_FUNC("can't find new rela",
 133                                  sec, offset + entry->new);
 134                        return -1;
 135                }
 136
 137                alt->new_sec = new_rela->sym->sec;
 138                alt->new_off = (unsigned int)new_rela->addend;
 139
 140                /* _ASM_EXTABLE_EX hack */
 141                if (alt->new_off >= 0x7ffffff0)
 142                        alt->new_off -= 0x7ffffff0;
 143        }
 144
 145        return 0;
 146}
 147
 148/*
 149 * Read all the special sections and create a list of special_alt structs which
 150 * describe all the alternate instructions which can be patched in or
 151 * redirected to at runtime.
 152 */
 153int special_get_alts(struct elf *elf, struct list_head *alts)
 154{
 155        struct special_entry *entry;
 156        struct section *sec;
 157        unsigned int nr_entries;
 158        struct special_alt *alt;
 159        int idx, ret;
 160
 161        INIT_LIST_HEAD(alts);
 162
 163        for (entry = entries; entry->sec; entry++) {
 164                sec = find_section_by_name(elf, entry->sec);
 165                if (!sec)
 166                        continue;
 167
 168                if (sec->len % entry->size != 0) {
 169                        WARN("%s size not a multiple of %d",
 170                             sec->name, entry->size);
 171                        return -1;
 172                }
 173
 174                nr_entries = sec->len / entry->size;
 175
 176                for (idx = 0; idx < nr_entries; idx++) {
 177                        alt = malloc(sizeof(*alt));
 178                        if (!alt) {
 179                                WARN("malloc failed");
 180                                return -1;
 181                        }
 182                        memset(alt, 0, sizeof(*alt));
 183
 184                        ret = get_alt_entry(elf, entry, sec, idx, alt);
 185                        if (ret)
 186                                return ret;
 187
 188                        list_add_tail(&alt->list, alts);
 189                }
 190        }
 191
 192        return 0;
 193}
 194