162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <stdlib.h>
762306a36Sopenharmony_ci#include <string.h>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/objtool_types.h>
1062306a36Sopenharmony_ci#include <asm/orc_types.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <objtool/check.h>
1362306a36Sopenharmony_ci#include <objtool/warn.h>
1462306a36Sopenharmony_ci#include <objtool/endianness.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic int init_orc_entry(struct orc_entry *orc, struct cfi_state *cfi,
1762306a36Sopenharmony_ci			  struct instruction *insn)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	struct cfi_reg *bp = &cfi->regs[CFI_BP];
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci	memset(orc, 0, sizeof(*orc));
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	if (!cfi) {
2462306a36Sopenharmony_ci		/*
2562306a36Sopenharmony_ci		 * This is usually either unreachable nops/traps (which don't
2662306a36Sopenharmony_ci		 * trigger unreachable instruction warnings), or
2762306a36Sopenharmony_ci		 * STACK_FRAME_NON_STANDARD functions.
2862306a36Sopenharmony_ci		 */
2962306a36Sopenharmony_ci		orc->type = ORC_TYPE_UNDEFINED;
3062306a36Sopenharmony_ci		return 0;
3162306a36Sopenharmony_ci	}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	switch (cfi->type) {
3462306a36Sopenharmony_ci	case UNWIND_HINT_TYPE_UNDEFINED:
3562306a36Sopenharmony_ci		orc->type = ORC_TYPE_UNDEFINED;
3662306a36Sopenharmony_ci		return 0;
3762306a36Sopenharmony_ci	case UNWIND_HINT_TYPE_END_OF_STACK:
3862306a36Sopenharmony_ci		orc->type = ORC_TYPE_END_OF_STACK;
3962306a36Sopenharmony_ci		return 0;
4062306a36Sopenharmony_ci	case UNWIND_HINT_TYPE_CALL:
4162306a36Sopenharmony_ci		orc->type = ORC_TYPE_CALL;
4262306a36Sopenharmony_ci		break;
4362306a36Sopenharmony_ci	case UNWIND_HINT_TYPE_REGS:
4462306a36Sopenharmony_ci		orc->type = ORC_TYPE_REGS;
4562306a36Sopenharmony_ci		break;
4662306a36Sopenharmony_ci	case UNWIND_HINT_TYPE_REGS_PARTIAL:
4762306a36Sopenharmony_ci		orc->type = ORC_TYPE_REGS_PARTIAL;
4862306a36Sopenharmony_ci		break;
4962306a36Sopenharmony_ci	default:
5062306a36Sopenharmony_ci		WARN_INSN(insn, "unknown unwind hint type %d", cfi->type);
5162306a36Sopenharmony_ci		return -1;
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	orc->signal = cfi->signal;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	switch (cfi->cfa.base) {
5762306a36Sopenharmony_ci	case CFI_SP:
5862306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_SP;
5962306a36Sopenharmony_ci		break;
6062306a36Sopenharmony_ci	case CFI_SP_INDIRECT:
6162306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_SP_INDIRECT;
6262306a36Sopenharmony_ci		break;
6362306a36Sopenharmony_ci	case CFI_BP:
6462306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_BP;
6562306a36Sopenharmony_ci		break;
6662306a36Sopenharmony_ci	case CFI_BP_INDIRECT:
6762306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_BP_INDIRECT;
6862306a36Sopenharmony_ci		break;
6962306a36Sopenharmony_ci	case CFI_R10:
7062306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_R10;
7162306a36Sopenharmony_ci		break;
7262306a36Sopenharmony_ci	case CFI_R13:
7362306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_R13;
7462306a36Sopenharmony_ci		break;
7562306a36Sopenharmony_ci	case CFI_DI:
7662306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_DI;
7762306a36Sopenharmony_ci		break;
7862306a36Sopenharmony_ci	case CFI_DX:
7962306a36Sopenharmony_ci		orc->sp_reg = ORC_REG_DX;
8062306a36Sopenharmony_ci		break;
8162306a36Sopenharmony_ci	default:
8262306a36Sopenharmony_ci		WARN_INSN(insn, "unknown CFA base reg %d", cfi->cfa.base);
8362306a36Sopenharmony_ci		return -1;
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	switch (bp->base) {
8762306a36Sopenharmony_ci	case CFI_UNDEFINED:
8862306a36Sopenharmony_ci		orc->bp_reg = ORC_REG_UNDEFINED;
8962306a36Sopenharmony_ci		break;
9062306a36Sopenharmony_ci	case CFI_CFA:
9162306a36Sopenharmony_ci		orc->bp_reg = ORC_REG_PREV_SP;
9262306a36Sopenharmony_ci		break;
9362306a36Sopenharmony_ci	case CFI_BP:
9462306a36Sopenharmony_ci		orc->bp_reg = ORC_REG_BP;
9562306a36Sopenharmony_ci		break;
9662306a36Sopenharmony_ci	default:
9762306a36Sopenharmony_ci		WARN_INSN(insn, "unknown BP base reg %d", bp->base);
9862306a36Sopenharmony_ci		return -1;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	orc->sp_offset = cfi->cfa.offset;
10262306a36Sopenharmony_ci	orc->bp_offset = bp->offset;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return 0;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int write_orc_entry(struct elf *elf, struct section *orc_sec,
10862306a36Sopenharmony_ci			   struct section *ip_sec, unsigned int idx,
10962306a36Sopenharmony_ci			   struct section *insn_sec, unsigned long insn_off,
11062306a36Sopenharmony_ci			   struct orc_entry *o)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct orc_entry *orc;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	/* populate ORC data */
11562306a36Sopenharmony_ci	orc = (struct orc_entry *)orc_sec->data->d_buf + idx;
11662306a36Sopenharmony_ci	memcpy(orc, o, sizeof(*orc));
11762306a36Sopenharmony_ci	orc->sp_offset = bswap_if_needed(elf, orc->sp_offset);
11862306a36Sopenharmony_ci	orc->bp_offset = bswap_if_needed(elf, orc->bp_offset);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* populate reloc for ip */
12162306a36Sopenharmony_ci	if (!elf_init_reloc_text_sym(elf, ip_sec, idx * sizeof(int), idx,
12262306a36Sopenharmony_ci				     insn_sec, insn_off))
12362306a36Sopenharmony_ci		return -1;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return 0;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistruct orc_list_entry {
12962306a36Sopenharmony_ci	struct list_head list;
13062306a36Sopenharmony_ci	struct orc_entry orc;
13162306a36Sopenharmony_ci	struct section *insn_sec;
13262306a36Sopenharmony_ci	unsigned long insn_off;
13362306a36Sopenharmony_ci};
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic int orc_list_add(struct list_head *orc_list, struct orc_entry *orc,
13662306a36Sopenharmony_ci			struct section *sec, unsigned long offset)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct orc_list_entry *entry = malloc(sizeof(*entry));
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!entry) {
14162306a36Sopenharmony_ci		WARN("malloc failed");
14262306a36Sopenharmony_ci		return -1;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	entry->orc	= *orc;
14662306a36Sopenharmony_ci	entry->insn_sec = sec;
14762306a36Sopenharmony_ci	entry->insn_off = offset;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	list_add_tail(&entry->list, orc_list);
15062306a36Sopenharmony_ci	return 0;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic unsigned long alt_group_len(struct alt_group *alt_group)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	return alt_group->last_insn->offset +
15662306a36Sopenharmony_ci	       alt_group->last_insn->len -
15762306a36Sopenharmony_ci	       alt_group->first_insn->offset;
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ciint orc_create(struct objtool_file *file)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct section *sec, *orc_sec;
16362306a36Sopenharmony_ci	unsigned int nr = 0, idx = 0;
16462306a36Sopenharmony_ci	struct orc_list_entry *entry;
16562306a36Sopenharmony_ci	struct list_head orc_list;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	struct orc_entry null = { .type = ORC_TYPE_UNDEFINED };
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	/* Build a deduplicated list of ORC entries: */
17062306a36Sopenharmony_ci	INIT_LIST_HEAD(&orc_list);
17162306a36Sopenharmony_ci	for_each_sec(file, sec) {
17262306a36Sopenharmony_ci		struct orc_entry orc, prev_orc = {0};
17362306a36Sopenharmony_ci		struct instruction *insn;
17462306a36Sopenharmony_ci		bool empty = true;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci		if (!sec->text)
17762306a36Sopenharmony_ci			continue;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci		sec_for_each_insn(file, sec, insn) {
18062306a36Sopenharmony_ci			struct alt_group *alt_group = insn->alt_group;
18162306a36Sopenharmony_ci			int i;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci			if (!alt_group) {
18462306a36Sopenharmony_ci				if (init_orc_entry(&orc, insn->cfi, insn))
18562306a36Sopenharmony_ci					return -1;
18662306a36Sopenharmony_ci				if (!memcmp(&prev_orc, &orc, sizeof(orc)))
18762306a36Sopenharmony_ci					continue;
18862306a36Sopenharmony_ci				if (orc_list_add(&orc_list, &orc, sec,
18962306a36Sopenharmony_ci						 insn->offset))
19062306a36Sopenharmony_ci					return -1;
19162306a36Sopenharmony_ci				nr++;
19262306a36Sopenharmony_ci				prev_orc = orc;
19362306a36Sopenharmony_ci				empty = false;
19462306a36Sopenharmony_ci				continue;
19562306a36Sopenharmony_ci			}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci			/*
19862306a36Sopenharmony_ci			 * Alternatives can have different stack layout
19962306a36Sopenharmony_ci			 * possibilities (but they shouldn't conflict).
20062306a36Sopenharmony_ci			 * Instead of traversing the instructions, use the
20162306a36Sopenharmony_ci			 * alt_group's flattened byte-offset-addressed CFI
20262306a36Sopenharmony_ci			 * array.
20362306a36Sopenharmony_ci			 */
20462306a36Sopenharmony_ci			for (i = 0; i < alt_group_len(alt_group); i++) {
20562306a36Sopenharmony_ci				struct cfi_state *cfi = alt_group->cfi[i];
20662306a36Sopenharmony_ci				if (!cfi)
20762306a36Sopenharmony_ci					continue;
20862306a36Sopenharmony_ci				/* errors are reported on the original insn */
20962306a36Sopenharmony_ci				if (init_orc_entry(&orc, cfi, insn))
21062306a36Sopenharmony_ci					return -1;
21162306a36Sopenharmony_ci				if (!memcmp(&prev_orc, &orc, sizeof(orc)))
21262306a36Sopenharmony_ci					continue;
21362306a36Sopenharmony_ci				if (orc_list_add(&orc_list, &orc, insn->sec,
21462306a36Sopenharmony_ci						 insn->offset + i))
21562306a36Sopenharmony_ci					return -1;
21662306a36Sopenharmony_ci				nr++;
21762306a36Sopenharmony_ci				prev_orc = orc;
21862306a36Sopenharmony_ci				empty = false;
21962306a36Sopenharmony_ci			}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci			/* Skip to the end of the alt_group */
22262306a36Sopenharmony_ci			insn = alt_group->last_insn;
22362306a36Sopenharmony_ci		}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		/* Add a section terminator */
22662306a36Sopenharmony_ci		if (!empty) {
22762306a36Sopenharmony_ci			orc_list_add(&orc_list, &null, sec, sec->sh.sh_size);
22862306a36Sopenharmony_ci			nr++;
22962306a36Sopenharmony_ci		}
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci	if (!nr)
23262306a36Sopenharmony_ci		return 0;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	/* Create .orc_unwind, .orc_unwind_ip and .rela.orc_unwind_ip sections: */
23562306a36Sopenharmony_ci	sec = find_section_by_name(file->elf, ".orc_unwind");
23662306a36Sopenharmony_ci	if (sec) {
23762306a36Sopenharmony_ci		WARN("file already has .orc_unwind section, skipping");
23862306a36Sopenharmony_ci		return -1;
23962306a36Sopenharmony_ci	}
24062306a36Sopenharmony_ci	orc_sec = elf_create_section(file->elf, ".orc_unwind",
24162306a36Sopenharmony_ci				     sizeof(struct orc_entry), nr);
24262306a36Sopenharmony_ci	if (!orc_sec)
24362306a36Sopenharmony_ci		return -1;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	sec = elf_create_section_pair(file->elf, ".orc_unwind_ip", sizeof(int), nr, nr);
24662306a36Sopenharmony_ci	if (!sec)
24762306a36Sopenharmony_ci		return -1;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* Write ORC entries to sections: */
25062306a36Sopenharmony_ci	list_for_each_entry(entry, &orc_list, list) {
25162306a36Sopenharmony_ci		if (write_orc_entry(file->elf, orc_sec, sec, idx++,
25262306a36Sopenharmony_ci				    entry->insn_sec, entry->insn_off,
25362306a36Sopenharmony_ci				    &entry->orc))
25462306a36Sopenharmony_ci			return -1;
25562306a36Sopenharmony_ci	}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	return 0;
25862306a36Sopenharmony_ci}
259