| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Copyright (C) 2017 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 | 4 | */ |
|---|
| 17 | 5 | |
|---|
| 18 | 6 | #include <stdlib.h> |
|---|
| 19 | 7 | #include <string.h> |
|---|
| 20 | 8 | |
|---|
| 21 | | -#include "orc.h" |
|---|
| 9 | +#include <linux/objtool.h> |
|---|
| 10 | +#include <asm/orc_types.h> |
|---|
| 11 | + |
|---|
| 22 | 12 | #include "check.h" |
|---|
| 23 | 13 | #include "warn.h" |
|---|
| 24 | 14 | |
|---|
| 25 | | -int create_orc(struct objtool_file *file) |
|---|
| 15 | +static int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi, |
|---|
| 16 | + struct instruction *insn) |
|---|
| 26 | 17 | { |
|---|
| 27 | | - struct instruction *insn; |
|---|
| 18 | + struct cfi_reg *bp = &cfi->regs[CFI_BP]; |
|---|
| 28 | 19 | |
|---|
| 29 | | - for_each_insn(file, insn) { |
|---|
| 30 | | - struct orc_entry *orc = &insn->orc; |
|---|
| 31 | | - struct cfi_reg *cfa = &insn->state.cfa; |
|---|
| 32 | | - struct cfi_reg *bp = &insn->state.regs[CFI_BP]; |
|---|
| 20 | + memset(orc, 0, sizeof(*orc)); |
|---|
| 33 | 21 | |
|---|
| 34 | | - orc->end = insn->state.end; |
|---|
| 35 | | - |
|---|
| 36 | | - if (cfa->base == CFI_UNDEFINED) { |
|---|
| 37 | | - orc->sp_reg = ORC_REG_UNDEFINED; |
|---|
| 38 | | - continue; |
|---|
| 39 | | - } |
|---|
| 40 | | - |
|---|
| 41 | | - switch (cfa->base) { |
|---|
| 42 | | - case CFI_SP: |
|---|
| 43 | | - orc->sp_reg = ORC_REG_SP; |
|---|
| 44 | | - break; |
|---|
| 45 | | - case CFI_SP_INDIRECT: |
|---|
| 46 | | - orc->sp_reg = ORC_REG_SP_INDIRECT; |
|---|
| 47 | | - break; |
|---|
| 48 | | - case CFI_BP: |
|---|
| 49 | | - orc->sp_reg = ORC_REG_BP; |
|---|
| 50 | | - break; |
|---|
| 51 | | - case CFI_BP_INDIRECT: |
|---|
| 52 | | - orc->sp_reg = ORC_REG_BP_INDIRECT; |
|---|
| 53 | | - break; |
|---|
| 54 | | - case CFI_R10: |
|---|
| 55 | | - orc->sp_reg = ORC_REG_R10; |
|---|
| 56 | | - break; |
|---|
| 57 | | - case CFI_R13: |
|---|
| 58 | | - orc->sp_reg = ORC_REG_R13; |
|---|
| 59 | | - break; |
|---|
| 60 | | - case CFI_DI: |
|---|
| 61 | | - orc->sp_reg = ORC_REG_DI; |
|---|
| 62 | | - break; |
|---|
| 63 | | - case CFI_DX: |
|---|
| 64 | | - orc->sp_reg = ORC_REG_DX; |
|---|
| 65 | | - break; |
|---|
| 66 | | - default: |
|---|
| 67 | | - WARN_FUNC("unknown CFA base reg %d", |
|---|
| 68 | | - insn->sec, insn->offset, cfa->base); |
|---|
| 69 | | - return -1; |
|---|
| 70 | | - } |
|---|
| 71 | | - |
|---|
| 72 | | - switch(bp->base) { |
|---|
| 73 | | - case CFI_UNDEFINED: |
|---|
| 74 | | - orc->bp_reg = ORC_REG_UNDEFINED; |
|---|
| 75 | | - break; |
|---|
| 76 | | - case CFI_CFA: |
|---|
| 77 | | - orc->bp_reg = ORC_REG_PREV_SP; |
|---|
| 78 | | - break; |
|---|
| 79 | | - case CFI_BP: |
|---|
| 80 | | - orc->bp_reg = ORC_REG_BP; |
|---|
| 81 | | - break; |
|---|
| 82 | | - default: |
|---|
| 83 | | - WARN_FUNC("unknown BP base reg %d", |
|---|
| 84 | | - insn->sec, insn->offset, bp->base); |
|---|
| 85 | | - return -1; |
|---|
| 86 | | - } |
|---|
| 87 | | - |
|---|
| 88 | | - orc->sp_offset = cfa->offset; |
|---|
| 89 | | - orc->bp_offset = bp->offset; |
|---|
| 90 | | - orc->type = insn->state.type; |
|---|
| 22 | + if (!cfi) { |
|---|
| 23 | + orc->end = 0; |
|---|
| 24 | + orc->sp_reg = ORC_REG_UNDEFINED; |
|---|
| 25 | + return 0; |
|---|
| 91 | 26 | } |
|---|
| 92 | 27 | |
|---|
| 93 | | - return 0; |
|---|
| 94 | | -} |
|---|
| 28 | + orc->end = cfi->end; |
|---|
| 95 | 29 | |
|---|
| 96 | | -static int create_orc_entry(struct section *u_sec, struct section *ip_relasec, |
|---|
| 97 | | - unsigned int idx, struct section *insn_sec, |
|---|
| 98 | | - unsigned long insn_off, struct orc_entry *o) |
|---|
| 99 | | -{ |
|---|
| 100 | | - struct orc_entry *orc; |
|---|
| 101 | | - struct rela *rela; |
|---|
| 30 | + if (cfi->cfa.base == CFI_UNDEFINED) { |
|---|
| 31 | + orc->sp_reg = ORC_REG_UNDEFINED; |
|---|
| 32 | + return 0; |
|---|
| 33 | + } |
|---|
| 102 | 34 | |
|---|
| 103 | | - /* populate ORC data */ |
|---|
| 104 | | - orc = (struct orc_entry *)u_sec->data->d_buf + idx; |
|---|
| 105 | | - memcpy(orc, o, sizeof(*orc)); |
|---|
| 106 | | - |
|---|
| 107 | | - /* populate rela for ip */ |
|---|
| 108 | | - rela = malloc(sizeof(*rela)); |
|---|
| 109 | | - if (!rela) { |
|---|
| 110 | | - perror("malloc"); |
|---|
| 35 | + switch (cfi->cfa.base) { |
|---|
| 36 | + case CFI_SP: |
|---|
| 37 | + orc->sp_reg = ORC_REG_SP; |
|---|
| 38 | + break; |
|---|
| 39 | + case CFI_SP_INDIRECT: |
|---|
| 40 | + orc->sp_reg = ORC_REG_SP_INDIRECT; |
|---|
| 41 | + break; |
|---|
| 42 | + case CFI_BP: |
|---|
| 43 | + orc->sp_reg = ORC_REG_BP; |
|---|
| 44 | + break; |
|---|
| 45 | + case CFI_BP_INDIRECT: |
|---|
| 46 | + orc->sp_reg = ORC_REG_BP_INDIRECT; |
|---|
| 47 | + break; |
|---|
| 48 | + case CFI_R10: |
|---|
| 49 | + orc->sp_reg = ORC_REG_R10; |
|---|
| 50 | + break; |
|---|
| 51 | + case CFI_R13: |
|---|
| 52 | + orc->sp_reg = ORC_REG_R13; |
|---|
| 53 | + break; |
|---|
| 54 | + case CFI_DI: |
|---|
| 55 | + orc->sp_reg = ORC_REG_DI; |
|---|
| 56 | + break; |
|---|
| 57 | + case CFI_DX: |
|---|
| 58 | + orc->sp_reg = ORC_REG_DX; |
|---|
| 59 | + break; |
|---|
| 60 | + default: |
|---|
| 61 | + WARN_FUNC("unknown CFA base reg %d", |
|---|
| 62 | + insn->sec, insn->offset, cfi->cfa.base); |
|---|
| 111 | 63 | return -1; |
|---|
| 112 | 64 | } |
|---|
| 113 | | - memset(rela, 0, sizeof(*rela)); |
|---|
| 114 | 65 | |
|---|
| 115 | | - if (insn_sec->sym) { |
|---|
| 116 | | - rela->sym = insn_sec->sym; |
|---|
| 117 | | - rela->addend = insn_off; |
|---|
| 118 | | - } else { |
|---|
| 119 | | - /* |
|---|
| 120 | | - * The Clang assembler doesn't produce section symbols, so we |
|---|
| 121 | | - * have to reference the function symbol instead: |
|---|
| 122 | | - */ |
|---|
| 123 | | - rela->sym = find_symbol_containing(insn_sec, insn_off); |
|---|
| 124 | | - if (!rela->sym) { |
|---|
| 125 | | - /* |
|---|
| 126 | | - * Hack alert. This happens when we need to reference |
|---|
| 127 | | - * the NOP pad insn immediately after the function. |
|---|
| 128 | | - */ |
|---|
| 129 | | - rela->sym = find_symbol_containing(insn_sec, |
|---|
| 130 | | - insn_off - 1); |
|---|
| 131 | | - } |
|---|
| 132 | | - if (!rela->sym) { |
|---|
| 133 | | - WARN("missing symbol for insn at offset 0x%lx\n", |
|---|
| 134 | | - insn_off); |
|---|
| 135 | | - return -1; |
|---|
| 136 | | - } |
|---|
| 137 | | - |
|---|
| 138 | | - rela->addend = insn_off - rela->sym->offset; |
|---|
| 66 | + switch (bp->base) { |
|---|
| 67 | + case CFI_UNDEFINED: |
|---|
| 68 | + orc->bp_reg = ORC_REG_UNDEFINED; |
|---|
| 69 | + break; |
|---|
| 70 | + case CFI_CFA: |
|---|
| 71 | + orc->bp_reg = ORC_REG_PREV_SP; |
|---|
| 72 | + break; |
|---|
| 73 | + case CFI_BP: |
|---|
| 74 | + orc->bp_reg = ORC_REG_BP; |
|---|
| 75 | + break; |
|---|
| 76 | + default: |
|---|
| 77 | + WARN_FUNC("unknown BP base reg %d", |
|---|
| 78 | + insn->sec, insn->offset, bp->base); |
|---|
| 79 | + return -1; |
|---|
| 139 | 80 | } |
|---|
| 140 | 81 | |
|---|
| 141 | | - rela->type = R_X86_64_PC32; |
|---|
| 142 | | - rela->offset = idx * sizeof(int); |
|---|
| 143 | | - |
|---|
| 144 | | - list_add_tail(&rela->list, &ip_relasec->rela_list); |
|---|
| 145 | | - hash_add(ip_relasec->rela_hash, &rela->hash, rela->offset); |
|---|
| 82 | + orc->sp_offset = cfi->cfa.offset; |
|---|
| 83 | + orc->bp_offset = bp->offset; |
|---|
| 84 | + orc->type = cfi->type; |
|---|
| 146 | 85 | |
|---|
| 147 | 86 | return 0; |
|---|
| 148 | 87 | } |
|---|
| 149 | 88 | |
|---|
| 150 | | -int create_orc_sections(struct objtool_file *file) |
|---|
| 89 | +static int write_orc_entry(struct elf *elf, struct section *orc_sec, |
|---|
| 90 | + struct section *ip_sec, unsigned int idx, |
|---|
| 91 | + struct section *insn_sec, unsigned long insn_off, |
|---|
| 92 | + struct orc_entry *o) |
|---|
| 151 | 93 | { |
|---|
| 152 | | - struct instruction *insn, *prev_insn; |
|---|
| 153 | | - struct section *sec, *u_sec, *ip_relasec; |
|---|
| 154 | | - unsigned int idx; |
|---|
| 94 | + struct orc_entry *orc; |
|---|
| 155 | 95 | |
|---|
| 156 | | - struct orc_entry empty = { |
|---|
| 157 | | - .sp_reg = ORC_REG_UNDEFINED, |
|---|
| 96 | + /* populate ORC data */ |
|---|
| 97 | + orc = (struct orc_entry *)orc_sec->data->d_buf + idx; |
|---|
| 98 | + memcpy(orc, o, sizeof(*orc)); |
|---|
| 99 | + |
|---|
| 100 | + /* populate reloc for ip */ |
|---|
| 101 | + if (elf_add_reloc_to_insn(elf, ip_sec, idx * sizeof(int), R_X86_64_PC32, |
|---|
| 102 | + insn_sec, insn_off)) |
|---|
| 103 | + return -1; |
|---|
| 104 | + |
|---|
| 105 | + return 0; |
|---|
| 106 | +} |
|---|
| 107 | + |
|---|
| 108 | +struct orc_list_entry { |
|---|
| 109 | + struct list_head list; |
|---|
| 110 | + struct orc_entry orc; |
|---|
| 111 | + struct section *insn_sec; |
|---|
| 112 | + unsigned long insn_off; |
|---|
| 113 | +}; |
|---|
| 114 | + |
|---|
| 115 | +static int orc_list_add(struct list_head *orc_list, struct orc_entry *orc, |
|---|
| 116 | + struct section *sec, unsigned long offset) |
|---|
| 117 | +{ |
|---|
| 118 | + struct orc_list_entry *entry = malloc(sizeof(*entry)); |
|---|
| 119 | + |
|---|
| 120 | + if (!entry) { |
|---|
| 121 | + WARN("malloc failed"); |
|---|
| 122 | + return -1; |
|---|
| 123 | + } |
|---|
| 124 | + |
|---|
| 125 | + entry->orc = *orc; |
|---|
| 126 | + entry->insn_sec = sec; |
|---|
| 127 | + entry->insn_off = offset; |
|---|
| 128 | + |
|---|
| 129 | + list_add_tail(&entry->list, orc_list); |
|---|
| 130 | + return 0; |
|---|
| 131 | +} |
|---|
| 132 | + |
|---|
| 133 | +static unsigned long alt_group_len(struct alt_group *alt_group) |
|---|
| 134 | +{ |
|---|
| 135 | + return alt_group->last_insn->offset + |
|---|
| 136 | + alt_group->last_insn->len - |
|---|
| 137 | + alt_group->first_insn->offset; |
|---|
| 138 | +} |
|---|
| 139 | + |
|---|
| 140 | +int orc_create(struct objtool_file *file) |
|---|
| 141 | +{ |
|---|
| 142 | + struct section *sec, *orc_sec; |
|---|
| 143 | + unsigned int nr = 0, idx = 0; |
|---|
| 144 | + struct orc_list_entry *entry; |
|---|
| 145 | + struct list_head orc_list; |
|---|
| 146 | + |
|---|
| 147 | + struct orc_entry null = { |
|---|
| 148 | + .sp_reg = ORC_REG_UNDEFINED, |
|---|
| 158 | 149 | .bp_reg = ORC_REG_UNDEFINED, |
|---|
| 159 | | - .type = ORC_TYPE_CALL, |
|---|
| 150 | + .type = UNWIND_HINT_TYPE_CALL, |
|---|
| 160 | 151 | }; |
|---|
| 161 | 152 | |
|---|
| 153 | + /* Build a deduplicated list of ORC entries: */ |
|---|
| 154 | + INIT_LIST_HEAD(&orc_list); |
|---|
| 155 | + for_each_sec(file, sec) { |
|---|
| 156 | + struct orc_entry orc, prev_orc = {0}; |
|---|
| 157 | + struct instruction *insn; |
|---|
| 158 | + bool empty = true; |
|---|
| 159 | + |
|---|
| 160 | + if (!sec->text) |
|---|
| 161 | + continue; |
|---|
| 162 | + |
|---|
| 163 | + sec_for_each_insn(file, sec, insn) { |
|---|
| 164 | + struct alt_group *alt_group = insn->alt_group; |
|---|
| 165 | + int i; |
|---|
| 166 | + |
|---|
| 167 | + if (!alt_group) { |
|---|
| 168 | + if (init_orc_entry(&orc, insn->cfi, insn)) |
|---|
| 169 | + return -1; |
|---|
| 170 | + if (!memcmp(&prev_orc, &orc, sizeof(orc))) |
|---|
| 171 | + continue; |
|---|
| 172 | + if (orc_list_add(&orc_list, &orc, sec, |
|---|
| 173 | + insn->offset)) |
|---|
| 174 | + return -1; |
|---|
| 175 | + nr++; |
|---|
| 176 | + prev_orc = orc; |
|---|
| 177 | + empty = false; |
|---|
| 178 | + continue; |
|---|
| 179 | + } |
|---|
| 180 | + |
|---|
| 181 | + /* |
|---|
| 182 | + * Alternatives can have different stack layout |
|---|
| 183 | + * possibilities (but they shouldn't conflict). |
|---|
| 184 | + * Instead of traversing the instructions, use the |
|---|
| 185 | + * alt_group's flattened byte-offset-addressed CFI |
|---|
| 186 | + * array. |
|---|
| 187 | + */ |
|---|
| 188 | + for (i = 0; i < alt_group_len(alt_group); i++) { |
|---|
| 189 | + struct cfi_state *cfi = alt_group->cfi[i]; |
|---|
| 190 | + if (!cfi) |
|---|
| 191 | + continue; |
|---|
| 192 | + /* errors are reported on the original insn */ |
|---|
| 193 | + if (init_orc_entry(&orc, cfi, insn)) |
|---|
| 194 | + return -1; |
|---|
| 195 | + if (!memcmp(&prev_orc, &orc, sizeof(orc))) |
|---|
| 196 | + continue; |
|---|
| 197 | + if (orc_list_add(&orc_list, &orc, insn->sec, |
|---|
| 198 | + insn->offset + i)) |
|---|
| 199 | + return -1; |
|---|
| 200 | + nr++; |
|---|
| 201 | + prev_orc = orc; |
|---|
| 202 | + empty = false; |
|---|
| 203 | + } |
|---|
| 204 | + |
|---|
| 205 | + /* Skip to the end of the alt_group */ |
|---|
| 206 | + insn = alt_group->last_insn; |
|---|
| 207 | + } |
|---|
| 208 | + |
|---|
| 209 | + /* Add a section terminator */ |
|---|
| 210 | + if (!empty) { |
|---|
| 211 | + orc_list_add(&orc_list, &null, sec, sec->len); |
|---|
| 212 | + nr++; |
|---|
| 213 | + } |
|---|
| 214 | + } |
|---|
| 215 | + if (!nr) |
|---|
| 216 | + return 0; |
|---|
| 217 | + |
|---|
| 218 | + /* Create .orc_unwind, .orc_unwind_ip and .rela.orc_unwind_ip sections: */ |
|---|
| 162 | 219 | sec = find_section_by_name(file->elf, ".orc_unwind"); |
|---|
| 163 | 220 | if (sec) { |
|---|
| 164 | 221 | WARN("file already has .orc_unwind section, skipping"); |
|---|
| 165 | 222 | return -1; |
|---|
| 166 | 223 | } |
|---|
| 167 | | - |
|---|
| 168 | | - /* count the number of needed orcs */ |
|---|
| 169 | | - idx = 0; |
|---|
| 170 | | - for_each_sec(file, sec) { |
|---|
| 171 | | - if (!sec->text) |
|---|
| 172 | | - continue; |
|---|
| 173 | | - |
|---|
| 174 | | - prev_insn = NULL; |
|---|
| 175 | | - sec_for_each_insn(file, sec, insn) { |
|---|
| 176 | | - if (!prev_insn || |
|---|
| 177 | | - memcmp(&insn->orc, &prev_insn->orc, |
|---|
| 178 | | - sizeof(struct orc_entry))) { |
|---|
| 179 | | - idx++; |
|---|
| 180 | | - } |
|---|
| 181 | | - prev_insn = insn; |
|---|
| 182 | | - } |
|---|
| 183 | | - |
|---|
| 184 | | - /* section terminator */ |
|---|
| 185 | | - if (prev_insn) |
|---|
| 186 | | - idx++; |
|---|
| 187 | | - } |
|---|
| 188 | | - if (!idx) |
|---|
| 224 | + orc_sec = elf_create_section(file->elf, ".orc_unwind", 0, |
|---|
| 225 | + sizeof(struct orc_entry), nr); |
|---|
| 226 | + if (!orc_sec) |
|---|
| 189 | 227 | return -1; |
|---|
| 190 | 228 | |
|---|
| 191 | | - |
|---|
| 192 | | - /* create .orc_unwind_ip and .rela.orc_unwind_ip sections */ |
|---|
| 193 | | - sec = elf_create_section(file->elf, ".orc_unwind_ip", sizeof(int), idx); |
|---|
| 229 | + sec = elf_create_section(file->elf, ".orc_unwind_ip", 0, sizeof(int), nr); |
|---|
| 194 | 230 | if (!sec) |
|---|
| 195 | 231 | return -1; |
|---|
| 196 | 232 | |
|---|
| 197 | | - ip_relasec = elf_create_rela_section(file->elf, sec); |
|---|
| 198 | | - if (!ip_relasec) |
|---|
| 199 | | - return -1; |
|---|
| 200 | | - |
|---|
| 201 | | - /* create .orc_unwind section */ |
|---|
| 202 | | - u_sec = elf_create_section(file->elf, ".orc_unwind", |
|---|
| 203 | | - sizeof(struct orc_entry), idx); |
|---|
| 204 | | - |
|---|
| 205 | | - /* populate sections */ |
|---|
| 206 | | - idx = 0; |
|---|
| 207 | | - for_each_sec(file, sec) { |
|---|
| 208 | | - if (!sec->text) |
|---|
| 209 | | - continue; |
|---|
| 210 | | - |
|---|
| 211 | | - prev_insn = NULL; |
|---|
| 212 | | - sec_for_each_insn(file, sec, insn) { |
|---|
| 213 | | - if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc, |
|---|
| 214 | | - sizeof(struct orc_entry))) { |
|---|
| 215 | | - |
|---|
| 216 | | - if (create_orc_entry(u_sec, ip_relasec, idx, |
|---|
| 217 | | - insn->sec, insn->offset, |
|---|
| 218 | | - &insn->orc)) |
|---|
| 219 | | - return -1; |
|---|
| 220 | | - |
|---|
| 221 | | - idx++; |
|---|
| 222 | | - } |
|---|
| 223 | | - prev_insn = insn; |
|---|
| 224 | | - } |
|---|
| 225 | | - |
|---|
| 226 | | - /* section terminator */ |
|---|
| 227 | | - if (prev_insn) { |
|---|
| 228 | | - if (create_orc_entry(u_sec, ip_relasec, idx, |
|---|
| 229 | | - prev_insn->sec, |
|---|
| 230 | | - prev_insn->offset + prev_insn->len, |
|---|
| 231 | | - &empty)) |
|---|
| 232 | | - return -1; |
|---|
| 233 | | - |
|---|
| 234 | | - idx++; |
|---|
| 235 | | - } |
|---|
| 233 | + /* Write ORC entries to sections: */ |
|---|
| 234 | + list_for_each_entry(entry, &orc_list, list) { |
|---|
| 235 | + if (write_orc_entry(file->elf, orc_sec, sec, idx++, |
|---|
| 236 | + entry->insn_sec, entry->insn_off, |
|---|
| 237 | + &entry->orc)) |
|---|
| 238 | + return -1; |
|---|
| 236 | 239 | } |
|---|
| 237 | | - |
|---|
| 238 | | - if (elf_rebuild_rela_section(ip_relasec)) |
|---|
| 239 | | - return -1; |
|---|
| 240 | 240 | |
|---|
| 241 | 241 | return 0; |
|---|
| 242 | 242 | } |
|---|