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