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