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