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