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_sec, unsigned int idx, 86 struct section *insn_sec, unsigned long insn_off, 87 struct orc_entry *o) 88 { 89 struct orc_entry *orc; 90 91 /* populate ORC data */ 92 orc = (struct orc_entry *)orc_sec->data->d_buf + idx; 93 memcpy(orc, o, sizeof(*orc)); 94 orc->sp_offset = bswap_if_needed(orc->sp_offset); 95 orc->bp_offset = bswap_if_needed(orc->bp_offset); 96 97 /* populate reloc for ip */ 98 if (elf_add_reloc_to_insn(elf, ip_sec, idx * sizeof(int), R_X86_64_PC32, 99 insn_sec, insn_off)) 100 return -1; 101 102 return 0; 103 } 104 105 struct orc_list_entry { 106 struct list_head list; 107 struct orc_entry orc; 108 struct section *insn_sec; 109 unsigned long insn_off; 110 }; 111 112 static int orc_list_add(struct list_head *orc_list, struct orc_entry *orc, 113 struct section *sec, unsigned long offset) 114 { 115 struct orc_list_entry *entry = malloc(sizeof(*entry)); 116 117 if (!entry) { 118 WARN("malloc failed"); 119 return -1; 120 } 121 122 entry->orc = *orc; 123 entry->insn_sec = sec; 124 entry->insn_off = offset; 125 126 list_add_tail(&entry->list, orc_list); 127 return 0; 128 } 129 130 static unsigned long alt_group_len(struct alt_group *alt_group) 131 { 132 return alt_group->last_insn->offset + 133 alt_group->last_insn->len - 134 alt_group->first_insn->offset; 135 } 136 137 int orc_create(struct objtool_file *file) 138 { 139 struct section *sec, *orc_sec; 140 unsigned int nr = 0, idx = 0; 141 struct orc_list_entry *entry; 142 struct list_head orc_list; 143 144 struct orc_entry null = { 145 .sp_reg = ORC_REG_UNDEFINED, 146 .bp_reg = ORC_REG_UNDEFINED, 147 .type = UNWIND_HINT_TYPE_CALL, 148 }; 149 150 /* Build a deduplicated list of ORC entries: */ 151 INIT_LIST_HEAD(&orc_list); 152 for_each_sec(file, sec) { 153 struct orc_entry orc, prev_orc = {0}; 154 struct instruction *insn; 155 bool empty = true; 156 157 if (!sec->text) 158 continue; 159 160 sec_for_each_insn(file, sec, insn) { 161 struct alt_group *alt_group = insn->alt_group; 162 int i; 163 164 if (!alt_group) { 165 if (init_orc_entry(&orc, &insn->cfi)) 166 return -1; 167 if (!memcmp(&prev_orc, &orc, sizeof(orc))) 168 continue; 169 if (orc_list_add(&orc_list, &orc, sec, 170 insn->offset)) 171 return -1; 172 nr++; 173 prev_orc = orc; 174 empty = false; 175 continue; 176 } 177 178 /* 179 * Alternatives can have different stack layout 180 * possibilities (but they shouldn't conflict). 181 * Instead of traversing the instructions, use the 182 * alt_group's flattened byte-offset-addressed CFI 183 * array. 184 */ 185 for (i = 0; i < alt_group_len(alt_group); i++) { 186 struct cfi_state *cfi = alt_group->cfi[i]; 187 if (!cfi) 188 continue; 189 if (init_orc_entry(&orc, cfi)) 190 return -1; 191 if (!memcmp(&prev_orc, &orc, sizeof(orc))) 192 continue; 193 if (orc_list_add(&orc_list, &orc, insn->sec, 194 insn->offset + i)) 195 return -1; 196 nr++; 197 prev_orc = orc; 198 empty = false; 199 } 200 201 /* Skip to the end of the alt_group */ 202 insn = alt_group->last_insn; 203 } 204 205 /* Add a section terminator */ 206 if (!empty) { 207 orc_list_add(&orc_list, &null, sec, sec->len); 208 nr++; 209 } 210 } 211 if (!nr) 212 return 0; 213 214 /* Create .orc_unwind, .orc_unwind_ip and .rela.orc_unwind_ip sections: */ 215 sec = find_section_by_name(file->elf, ".orc_unwind"); 216 if (sec) { 217 WARN("file already has .orc_unwind section, skipping"); 218 return -1; 219 } 220 orc_sec = elf_create_section(file->elf, ".orc_unwind", 0, 221 sizeof(struct orc_entry), nr); 222 if (!orc_sec) 223 return -1; 224 225 sec = elf_create_section(file->elf, ".orc_unwind_ip", 0, sizeof(int), nr); 226 if (!sec) 227 return -1; 228 229 /* Write ORC entries to sections: */ 230 list_for_each_entry(entry, &orc_list, list) { 231 if (write_orc_entry(file->elf, orc_sec, sec, idx++, 232 entry->insn_sec, entry->insn_off, 233 &entry->orc)) 234 return -1; 235 } 236 237 return 0; 238 } 239