linux/tools/objtool/arch/loongarch/special.c
Tiezhu Yang a47bc954cf objtool/LoongArch: Get table size correctly if LTO is enabled
When compiling with LLVM and CONFIG_LTO_CLANG is set, there exist many
objtool warnings "sibling call from callable instruction with modified
stack frame".

For this special case, the related object file shows that there is no
generated relocation section '.rela.discard.tablejump_annotate' for the
table jump instruction jirl, thus objtool can not know that what is the
actual destination address.

It needs to do something on the LLVM side to make sure that there is the
relocation section '.rela.discard.tablejump_annotate' if LTO is enabled,
but in order to maintain compatibility for the current LLVM compiler,
this can be done in the kernel Makefile for now. Ensure it is aware of
linker with LTO, '--loongarch-annotate-tablejump' needs to be passed via
'-mllvm' to ld.lld.

Before doing the above changes, it should handle the special case of the
relocation section '.rela.discard.tablejump_annotate' to get the correct
table size first, otherwise there are many objtool warnings and errors
if LTO is enabled.

There are many different rodata for each function if LTO is enabled, it
is necessary to enhance get_rodata_table_size_by_table_annotate().

Fixes: b95f852d3a ("objtool/LoongArch: Add support for switch table")
Closes: https://lore.kernel.org/loongarch/20250731175655.GA1455142@ax162/
Reported-by: Nathan Chancellor <nathan@kernel.org>
Tested-by: Nathan Chancellor <nathan@kernel.org>
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
2025-08-20 22:23:15 +08:00

197 lines
4.8 KiB
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;
}