root/tools/objtool/arch/loongarch/special.c
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <objtool/special.h>
#include <objtool/warn.h>

bool arch_support_alt_relocation(struct special_alt *special_alt,
                                 struct instruction *insn,
                                 struct reloc *reloc)
{
        return false;
}

struct table_info {
        struct list_head jump_info;
        unsigned long insn_offset;
        unsigned long rodata_offset;
};

static void get_rodata_table_size_by_table_annotate(struct objtool_file *file,
                                                    struct instruction *insn,
                                                    unsigned long *table_size)
{
        struct section *rsec;
        struct reloc *reloc;
        struct list_head table_list;
        struct table_info *orig_table;
        struct table_info *next_table;
        unsigned long tmp_insn_offset;
        unsigned long tmp_rodata_offset;
        bool is_valid_list = false;

        rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
        if (!rsec)
                return;

        INIT_LIST_HEAD(&table_list);

        for_each_reloc(rsec, reloc) {
                if (reloc->sym->sec->rodata)
                        continue;

                if (strcmp(insn->sec->name, reloc->sym->sec->name))
                        continue;

                orig_table = malloc(sizeof(struct table_info));
                if (!orig_table) {
                        WARN("malloc failed");
                        return;
                }

                orig_table->insn_offset = reloc->sym->offset + reloc_addend(reloc);
                reloc++;
                orig_table->rodata_offset = reloc->sym->offset + reloc_addend(reloc);

                list_add_tail(&orig_table->jump_info, &table_list);

                if (reloc_idx(reloc) + 1 == sec_num_entries(rsec))
                        break;

                if (strcmp(insn->sec->name, (reloc + 1)->sym->sec->name)) {
                        list_for_each_entry(orig_table, &table_list, jump_info) {
                                if (orig_table->insn_offset == insn->offset) {
                                        is_valid_list = true;
                                        break;
                                }
                        }

                        if (!is_valid_list) {
                                list_del_init(&table_list);
                                continue;
                        }

                        break;
                }
        }

        list_for_each_entry(orig_table, &table_list, jump_info) {
                next_table = list_next_entry(orig_table, jump_info);
                list_for_each_entry_from(next_table, &table_list, jump_info) {
                        if (next_table->rodata_offset < orig_table->rodata_offset) {
                                tmp_insn_offset = next_table->insn_offset;
                                tmp_rodata_offset = next_table->rodata_offset;
                                next_table->insn_offset = orig_table->insn_offset;
                                next_table->rodata_offset = orig_table->rodata_offset;
                                orig_table->insn_offset = tmp_insn_offset;
                                orig_table->rodata_offset = tmp_rodata_offset;
                        }
                }
        }

        list_for_each_entry(orig_table, &table_list, jump_info) {
                if (insn->offset == orig_table->insn_offset) {
                        next_table = list_next_entry(orig_table, jump_info);
                        if (&next_table->jump_info == &table_list) {
                                *table_size = 0;
                                return;
                        }

                        while (next_table->rodata_offset == orig_table->rodata_offset) {
                                next_table = list_next_entry(next_table, jump_info);
                                if (&next_table->jump_info == &table_list) {
                                        *table_size = 0;
                                        return;
                                }
                        }

                        *table_size = next_table->rodata_offset - orig_table->rodata_offset;
                }
        }
}

static struct reloc *find_reloc_by_table_annotate(struct objtool_file *file,
                                                  struct instruction *insn,
                                                  unsigned long *table_size)
{
        struct section *rsec;
        struct reloc *reloc;
        unsigned long offset;

        rsec = find_section_by_name(file->elf, ".rela.discard.tablejump_annotate");
        if (!rsec)
                return NULL;

        for_each_reloc(rsec, reloc) {
                if (reloc->sym->sec->rodata)
                        continue;

                if (strcmp(insn->sec->name, reloc->sym->sec->name))
                        continue;

                offset = reloc->sym->offset + reloc_addend(reloc);
                if (insn->offset == offset) {
                        get_rodata_table_size_by_table_annotate(file, insn, table_size);
                        reloc++;
                        return reloc;
                }
        }

        return NULL;
}

static struct reloc *find_reloc_of_rodata_c_jump_table(struct section *sec,
                                                       unsigned long offset,
                                                       unsigned long *table_size)
{
        struct section *rsec;
        struct reloc *reloc;

        rsec = sec->rsec;
        if (!rsec)
                return NULL;

        for_each_reloc(rsec, reloc) {
                if (reloc_offset(reloc) > offset)
                        break;

                if (!strcmp(reloc->sym->sec->name, C_JUMP_TABLE_SECTION)) {
                        *table_size = 0;
                        return reloc;
                }
        }

        return NULL;
}

struct reloc *arch_find_switch_table(struct objtool_file *file,
                                     struct instruction *insn,
                                     unsigned long *table_size)
{
        struct reloc *annotate_reloc;
        struct reloc *rodata_reloc;
        struct section *table_sec;
        unsigned long table_offset;

        annotate_reloc = find_reloc_by_table_annotate(file, insn, table_size);
        if (!annotate_reloc) {
                annotate_reloc = find_reloc_of_rodata_c_jump_table(
                                 insn->sec, insn->offset, table_size);
                if (!annotate_reloc)
                        return NULL;
        }

        table_sec = annotate_reloc->sym->sec;
        table_offset = annotate_reloc->sym->offset + reloc_addend(annotate_reloc);

        /*
         * Each table entry has a rela associated with it.  The rela
         * should reference text in the same function as the original
         * instruction.
         */
        rodata_reloc = find_reloc_by_dest(file->elf, table_sec, table_offset);
        if (!rodata_reloc)
                return NULL;

        return rodata_reloc;
}

const char *arch_cpu_feature_name(int feature_number)
{
        return NULL;
}