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 15 int create_orc(struct objtool_file *file) 16 { 17 struct instruction *insn; 18 19 for_each_insn(file, insn) { 20 struct orc_entry *orc = &insn->orc; 21 struct cfi_reg *cfa = &insn->cfi.cfa; 22 struct cfi_reg *bp = &insn->cfi.regs[CFI_BP]; 23 24 if (!insn->sec->text) 25 continue; 26 27 orc->end = insn->cfi.end; 28 29 if (cfa->base == CFI_UNDEFINED) { 30 orc->sp_reg = ORC_REG_UNDEFINED; 31 continue; 32 } 33 34 switch (cfa->base) { 35 case CFI_SP: 36 orc->sp_reg = ORC_REG_SP; 37 break; 38 case CFI_SP_INDIRECT: 39 orc->sp_reg = ORC_REG_SP_INDIRECT; 40 break; 41 case CFI_BP: 42 orc->sp_reg = ORC_REG_BP; 43 break; 44 case CFI_BP_INDIRECT: 45 orc->sp_reg = ORC_REG_BP_INDIRECT; 46 break; 47 case CFI_R10: 48 orc->sp_reg = ORC_REG_R10; 49 break; 50 case CFI_R13: 51 orc->sp_reg = ORC_REG_R13; 52 break; 53 case CFI_DI: 54 orc->sp_reg = ORC_REG_DI; 55 break; 56 case CFI_DX: 57 orc->sp_reg = ORC_REG_DX; 58 break; 59 default: 60 WARN_FUNC("unknown CFA base reg %d", 61 insn->sec, insn->offset, cfa->base); 62 return -1; 63 } 64 65 switch(bp->base) { 66 case CFI_UNDEFINED: 67 orc->bp_reg = ORC_REG_UNDEFINED; 68 break; 69 case CFI_CFA: 70 orc->bp_reg = ORC_REG_PREV_SP; 71 break; 72 case CFI_BP: 73 orc->bp_reg = ORC_REG_BP; 74 break; 75 default: 76 WARN_FUNC("unknown BP base reg %d", 77 insn->sec, insn->offset, bp->base); 78 return -1; 79 } 80 81 orc->sp_offset = cfa->offset; 82 orc->bp_offset = bp->offset; 83 orc->type = insn->cfi.type; 84 } 85 86 return 0; 87 } 88 89 static int create_orc_entry(struct elf *elf, struct section *u_sec, struct section *ip_relocsec, 90 unsigned int idx, struct section *insn_sec, 91 unsigned long insn_off, struct orc_entry *o) 92 { 93 struct orc_entry *orc; 94 struct reloc *reloc; 95 96 /* populate ORC data */ 97 orc = (struct orc_entry *)u_sec->data->d_buf + idx; 98 memcpy(orc, o, sizeof(*orc)); 99 100 /* populate reloc for ip */ 101 reloc = malloc(sizeof(*reloc)); 102 if (!reloc) { 103 perror("malloc"); 104 return -1; 105 } 106 memset(reloc, 0, sizeof(*reloc)); 107 108 insn_to_reloc_sym_addend(insn_sec, insn_off, reloc); 109 if (!reloc->sym) { 110 WARN("missing symbol for insn at offset 0x%lx", 111 insn_off); 112 return -1; 113 } 114 115 reloc->type = R_X86_64_PC32; 116 reloc->offset = idx * sizeof(int); 117 reloc->sec = ip_relocsec; 118 119 elf_add_reloc(elf, reloc); 120 121 return 0; 122 } 123 124 int create_orc_sections(struct objtool_file *file) 125 { 126 struct instruction *insn, *prev_insn; 127 struct section *sec, *u_sec, *ip_relocsec; 128 unsigned int idx; 129 130 struct orc_entry empty = { 131 .sp_reg = ORC_REG_UNDEFINED, 132 .bp_reg = ORC_REG_UNDEFINED, 133 .type = UNWIND_HINT_TYPE_CALL, 134 }; 135 136 sec = find_section_by_name(file->elf, ".orc_unwind"); 137 if (sec) { 138 WARN("file already has .orc_unwind section, skipping"); 139 return -1; 140 } 141 142 /* count the number of needed orcs */ 143 idx = 0; 144 for_each_sec(file, sec) { 145 if (!sec->text) 146 continue; 147 148 prev_insn = NULL; 149 sec_for_each_insn(file, sec, insn) { 150 if (!prev_insn || 151 memcmp(&insn->orc, &prev_insn->orc, 152 sizeof(struct orc_entry))) { 153 idx++; 154 } 155 prev_insn = insn; 156 } 157 158 /* section terminator */ 159 if (prev_insn) 160 idx++; 161 } 162 if (!idx) 163 return -1; 164 165 166 /* create .orc_unwind_ip and .rela.orc_unwind_ip sections */ 167 sec = elf_create_section(file->elf, ".orc_unwind_ip", 0, sizeof(int), idx); 168 if (!sec) 169 return -1; 170 171 ip_relocsec = elf_create_reloc_section(file->elf, sec, SHT_RELA); 172 if (!ip_relocsec) 173 return -1; 174 175 /* create .orc_unwind section */ 176 u_sec = elf_create_section(file->elf, ".orc_unwind", 0, 177 sizeof(struct orc_entry), idx); 178 179 /* populate sections */ 180 idx = 0; 181 for_each_sec(file, sec) { 182 if (!sec->text) 183 continue; 184 185 prev_insn = NULL; 186 sec_for_each_insn(file, sec, insn) { 187 if (!prev_insn || memcmp(&insn->orc, &prev_insn->orc, 188 sizeof(struct orc_entry))) { 189 190 if (create_orc_entry(file->elf, u_sec, ip_relocsec, idx, 191 insn->sec, insn->offset, 192 &insn->orc)) 193 return -1; 194 195 idx++; 196 } 197 prev_insn = insn; 198 } 199 200 /* section terminator */ 201 if (prev_insn) { 202 if (create_orc_entry(file->elf, u_sec, ip_relocsec, idx, 203 prev_insn->sec, 204 prev_insn->offset + prev_insn->len, 205 &empty)) 206 return -1; 207 208 idx++; 209 } 210 } 211 212 if (elf_rebuild_reloc_section(file->elf, ip_relocsec)) 213 return -1; 214 215 return 0; 216 } 217