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