| From 6f5ec2993b1f39aed12fa6fd56e8dc2272ee8a33 Mon Sep 17 00:00:00 2001 |
| From: Josh Poimboeuf <jpoimboe@redhat.com> |
| Date: Mon, 14 May 2018 08:53:24 -0500 |
| Subject: objtool: Detect RIP-relative switch table references |
| |
| From: Josh Poimboeuf <jpoimboe@redhat.com> |
| |
| commit 6f5ec2993b1f39aed12fa6fd56e8dc2272ee8a33 upstream. |
| |
| Typically a switch table can be found by detecting a .rodata access |
| followed an indirect jump: |
| |
| 1969: 4a 8b 0c e5 00 00 00 mov 0x0(,%r12,8),%rcx |
| 1970: 00 |
| 196d: R_X86_64_32S .rodata+0x438 |
| 1971: e9 00 00 00 00 jmpq 1976 <dispc_runtime_suspend+0xb6a> |
| 1972: R_X86_64_PC32 __x86_indirect_thunk_rcx-0x4 |
| |
| Randy Dunlap reported a case (seen with GCC 4.8) where the .rodata |
| access uses RIP-relative addressing: |
| |
| 19bd: 48 8b 3d 00 00 00 00 mov 0x0(%rip),%rdi # 19c4 <dispc_runtime_suspend+0xbb8> |
| 19c0: R_X86_64_PC32 .rodata+0x45c |
| 19c4: e9 00 00 00 00 jmpq 19c9 <dispc_runtime_suspend+0xbbd> |
| 19c5: R_X86_64_PC32 __x86_indirect_thunk_rdi-0x4 |
| |
| In this case the relocation addend needs to be adjusted accordingly in |
| order to find the location of the switch table. |
| |
| The fix is for case 3 (as described in the comments), but also make the |
| existing case 1 & 2 checks more precise by only adjusting the addend for |
| R_X86_64_PC32 relocations. |
| |
| This fixes the following warnings: |
| |
| drivers/video/fbdev/omap2/omapfb/dss/dispc.o: warning: objtool: dispc_runtime_suspend()+0xbb8: sibling call from callable instruction with modified stack frame |
| drivers/video/fbdev/omap2/omapfb/dss/dispc.o: warning: objtool: dispc_runtime_resume()+0xcc5: sibling call from callable instruction with modified stack frame |
| |
| Reported-by: Randy Dunlap <rdunlap@infradead.org> |
| Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> |
| Cc: Linus Torvalds <torvalds@linux-foundation.org> |
| Cc: Peter Zijlstra <peterz@infradead.org> |
| Cc: Thomas Gleixner <tglx@linutronix.de> |
| Link: http://lkml.kernel.org/r/b6098294fd67afb69af8c47c9883d7a68bf0f8ea.1526305958.git.jpoimboe@redhat.com |
| Signed-off-by: Ingo Molnar <mingo@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| tools/objtool/check.c | 33 ++++++++++++++++++--------------- |
| 1 file changed, 18 insertions(+), 15 deletions(-) |
| |
| --- a/tools/objtool/check.c |
| +++ b/tools/objtool/check.c |
| @@ -898,24 +898,24 @@ static struct rela *find_switch_table(st |
| { |
| struct rela *text_rela, *rodata_rela; |
| struct instruction *orig_insn = insn; |
| + unsigned long table_offset; |
| |
| + /* case 1 & 2 */ |
| text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len); |
| if (text_rela && text_rela->sym == file->rodata->sym && |
| !find_symbol_containing(file->rodata, text_rela->addend)) { |
| |
| - /* case 1 */ |
| - rodata_rela = find_rela_by_dest(file->rodata, |
| - text_rela->addend); |
| - if (rodata_rela) |
| - return rodata_rela; |
| + table_offset = text_rela->addend; |
| + if (text_rela->type == R_X86_64_PC32) { |
| + /* case 2 */ |
| + table_offset += 4; |
| + file->ignore_unreachables = true; |
| + } |
| |
| - /* case 2 */ |
| - rodata_rela = find_rela_by_dest(file->rodata, |
| - text_rela->addend + 4); |
| + rodata_rela = find_rela_by_dest(file->rodata, table_offset); |
| if (!rodata_rela) |
| return NULL; |
| |
| - file->ignore_unreachables = true; |
| return rodata_rela; |
| } |
| |
| @@ -949,18 +949,21 @@ static struct rela *find_switch_table(st |
| if (!text_rela || text_rela->sym != file->rodata->sym) |
| continue; |
| |
| + table_offset = text_rela->addend; |
| + if (text_rela->type == R_X86_64_PC32) |
| + table_offset += 4; |
| + |
| /* |
| * Make sure the .rodata address isn't associated with a |
| * symbol. gcc jump tables are anonymous data. |
| */ |
| - if (find_symbol_containing(file->rodata, text_rela->addend)) |
| + if (find_symbol_containing(file->rodata, table_offset)) |
| continue; |
| |
| - rodata_rela = find_rela_by_dest(file->rodata, text_rela->addend); |
| - if (!rodata_rela) |
| - continue; |
| - |
| - return rodata_rela; |
| + /* mov [rodata addr], %reg */ |
| + rodata_rela = find_rela_by_dest(file->rodata, table_offset); |
| + if (rodata_rela) |
| + return rodata_rela; |
| } |
| |
| return NULL; |