[v5,5/8] objtool: Check local label about sibling call
Commit Message
When update the latest upstream gcc and binutils which enables linker
relaxation by default, it generates more objtool warnings on LoongArch,
like this:
init/version.o: warning: objtool: early_hostname+0x1c: sibling call from callable instruction with modified stack frame
We can see that the branch and jump operation about local label ".L2"
is not sibling call, because a sibling call is a tail-call to another
symbol. In this case, make is_sibling_call() return false, set dest_sec
and dest_off to calculate jump_dest in add_jump_destinations().
As suggested by Peter Zijlstra, add "local_label" member in struct
symbol, then set it as true if symbol type is STT_NOTYPE and symbol
name starts with ".L" string in classify_symbols().
Here are some detailed info:
[fedora@linux 6.7.test]$ gcc --version
gcc (GCC) 14.0.0 20231127 (experimental)
[fedora@linux 6.7.test]$ as --version
GNU assembler (GNU Binutils) 2.41.50.20231127
[fedora@linux 6.7.test]$ objdump -M no-aliases -D init/version.o | grep -A 20 "init.text"
Disassembly of section .init.text:
0000000000000000 <early_hostname>:
0: 1a00000c pcalau12i $t0, 0
4: 02ffc063 addi.d $sp, $sp, -16
8: 00150085 or $a1, $a0, $zero
c: 02810406 addi.w $a2, $zero, 65
10: 02c00184 addi.d $a0, $t0, 0
14: 29c02061 st.d $ra, $sp, 8
18: 54000000 bl 0 # 18 <early_hostname+0x18>
1c: 64001480 bge $a0, $zero, 20 # 30 <.L2>
20: 1a000004 pcalau12i $a0, 0
24: 02810005 addi.w $a1, $zero, 64
28: 02c00084 addi.d $a0, $a0, 0
2c: 54000000 bl 0 # 2c <early_hostname+0x2c>
0000000000000030 <.L2>:
30: 28c02061 ld.d $ra, $sp, 8
34: 00150004 or $a0, $zero, $zero
38: 02c04063 addi.d $sp, $sp, 16
3c: 4c000020 jirl $zero, $ra, 0
By the way, it need to move insn_reloc() before is_sibling_call()
to avoid implicit declaration build error.
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
Reviewed-by: Huacai Chen <chenhuacai@loongson.cn>
---
tools/objtool/check.c | 71 +++++++++++++++++------------
tools/objtool/include/objtool/elf.h | 1 +
2 files changed, 44 insertions(+), 28 deletions(-)
@@ -20,6 +20,7 @@
#include <linux/hashtable.h>
#include <linux/kernel.h>
#include <linux/static_call_types.h>
+#include <linux/string.h>
struct alternative {
struct alternative *next;
@@ -161,12 +162,38 @@ static bool is_jump_table_jump(struct instruction *insn)
insn_jump_table(alt_group->orig_group->first_insn);
}
-static bool is_sibling_call(struct instruction *insn)
+static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
+{
+ struct reloc *reloc;
+
+ if (insn->no_reloc)
+ return NULL;
+
+ if (!file)
+ return NULL;
+
+ reloc = find_reloc_by_dest_range(file->elf, insn->sec,
+ insn->offset, insn->len);
+ if (!reloc) {
+ insn->no_reloc = 1;
+ return NULL;
+ }
+
+ return reloc;
+}
+
+static bool is_sibling_call(struct objtool_file *file, struct instruction *insn)
{
/*
* Assume only STT_FUNC calls have jump-tables.
*/
if (insn_func(insn)) {
+ struct reloc *reloc = insn_reloc(file, insn);
+
+ /* Disallow sibling calls into STT_NOTYPE if it is local lable */
+ if (reloc && reloc->sym->local_label)
+ return false;
+
/* An indirect jump is either a sibling call or a jump to a table. */
if (insn->type == INSN_JUMP_DYNAMIC)
return !is_jump_table_jump(insn);
@@ -232,7 +259,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
* of the sibling call returns.
*/
func_for_each_insn(file, func, insn) {
- if (is_sibling_call(insn)) {
+ if (is_sibling_call(file, insn)) {
struct instruction *dest = insn->jump_dest;
if (!dest)
@@ -743,7 +770,7 @@ static int create_static_call_sections(struct objtool_file *file)
if (!elf_init_reloc_data_sym(file->elf, sec,
idx * sizeof(*site) + 4,
(idx * 2) + 1, key_sym,
- is_sibling_call(insn) * STATIC_CALL_SITE_TAIL))
+ is_sibling_call(file, insn) * STATIC_CALL_SITE_TAIL))
return -1;
idx++;
@@ -1315,26 +1342,6 @@ __weak bool arch_is_embedded_insn(struct symbol *sym)
return false;
}
-static struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn)
-{
- struct reloc *reloc;
-
- if (insn->no_reloc)
- return NULL;
-
- if (!file)
- return NULL;
-
- reloc = find_reloc_by_dest_range(file->elf, insn->sec,
- insn->offset, insn->len);
- if (!reloc) {
- insn->no_reloc = 1;
- return NULL;
- }
-
- return reloc;
-}
-
static void remove_insn_ops(struct instruction *insn)
{
struct stack_op *op, *next;
@@ -1577,8 +1584,13 @@ static int add_jump_destinations(struct objtool_file *file)
* External sibling call or internal sibling call with
* STT_FUNC reloc.
*/
- add_call_dest(file, insn, reloc->sym, true);
- continue;
+ if (reloc->sym->local_label) {
+ dest_sec = insn->sec;
+ dest_off = arch_jump_destination(insn);
+ } else {
+ add_call_dest(file, insn, reloc->sym, true);
+ continue;
+ }
} else if (reloc->sym->sec->idx) {
dest_sec = reloc->sym->sec;
dest_off = reloc->sym->sym.st_value +
@@ -2522,6 +2534,9 @@ static int classify_symbols(struct objtool_file *file)
struct symbol *func;
for_each_sym(file, func) {
+ if (func->type == STT_NOTYPE && strstarts(func->name, ".L"))
+ func->local_label = true;
+
if (func->bind != STB_GLOBAL)
continue;
@@ -3690,7 +3705,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
case INSN_JUMP_CONDITIONAL:
case INSN_JUMP_UNCONDITIONAL:
- if (is_sibling_call(insn)) {
+ if (is_sibling_call(file, insn)) {
ret = validate_sibling_call(file, insn, &state);
if (ret)
return ret;
@@ -3711,7 +3726,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
case INSN_JUMP_DYNAMIC:
case INSN_JUMP_DYNAMIC_CONDITIONAL:
- if (is_sibling_call(insn)) {
+ if (is_sibling_call(file, insn)) {
ret = validate_sibling_call(file, insn, &state);
if (ret)
return ret;
@@ -3875,7 +3890,7 @@ static int validate_unret(struct objtool_file *file, struct instruction *insn)
case INSN_JUMP_UNCONDITIONAL:
case INSN_JUMP_CONDITIONAL:
- if (!is_sibling_call(insn)) {
+ if (!is_sibling_call(file, insn)) {
if (!insn->jump_dest) {
WARN_INSN(insn, "unresolved jump target after linking?!?");
return -1;
@@ -67,6 +67,7 @@ struct symbol {
u8 profiling_func : 1;
u8 warned : 1;
u8 embedded_insn : 1;
+ u8 local_label : 1;
struct list_head pv_target;
struct reloc *relocs;
};