Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

objtool: Fix false positive warnings for functions with multiple switch statements

Ingo reported [1] some false positive objtool warnings:

drivers/net/wireless/realtek/rtlwifi/base.o: warning: objtool: rtlwifi_rate_mapping()+0x2e7: frame pointer state mismatch
drivers/net/wireless/realtek/rtlwifi/base.o: warning: objtool: rtlwifi_rate_mapping()+0x2f3: frame pointer state mismatch
...

And so did the 0-day bot [2]:

drivers/gpu/drm/radeon/cik.o: warning: objtool: cik_tiling_mode_table_init()+0x6ce: call without frame pointer save/setup
drivers/gpu/drm/radeon/cik.o: warning: objtool: cik_tiling_mode_table_init()+0x72b: call without frame pointer save/setup
...

Both sets of warnings involve functions which have multiple switch
statements. When there's more than one switch statement in a function,
objtool interprets all the switch jump tables as a single table. If the
targets of one jump table assume a stack frame and the targets of
another one don't, it prints false positive warnings.

Fix the bug by detecting the size of each switch jump table. For
multiple tables, each one ends where the next one begins.

[1] https://lkml.kernel.org/r/20160308103716.GA9618@gmail.com
[2] https://lists.01.org/pipermail/kbuild-all/2016-March/018124.html

Reported-by: Ingo Molnar <mingo@kernel.org>
Reported-by: kbuild test robot <fengguang.wu@intel.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Arnaldo Carvalho de Melo <acme@infradead.org>
Cc: Arnaldo Carvalho de Melo <acme@kernel.org>
Cc: Bernd Petrovitsch <bernd@petrovitsch.priv.at>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Chris J Arges <chris.j.arges@canonical.com>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Michal Marek <mmarek@suse.cz>
Cc: Namhyung Kim <namhyung@gmail.com>
Cc: Pedro Alves <palves@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: live-patching@vger.kernel.org
Link: http://lkml.kernel.org/r/2d7eecc6bc52d301f494b80f5fd62c2b6c895658.1457502970.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Josh Poimboeuf and committed by
Ingo Molnar
8133fbb4 a196e171

+111 -56
+111 -56
tools/objtool/builtin-check.c
··· 61 61 struct objtool_file { 62 62 struct elf *elf; 63 63 struct list_head insn_list; 64 + struct section *rodata; 64 65 }; 65 66 66 67 const char *objname; ··· 600 599 return ret; 601 600 } 602 601 602 + static int add_switch_table(struct objtool_file *file, struct symbol *func, 603 + struct instruction *insn, struct rela *table, 604 + struct rela *next_table) 605 + { 606 + struct rela *rela = table; 607 + struct instruction *alt_insn; 608 + struct alternative *alt; 609 + 610 + list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) { 611 + if (rela == next_table) 612 + break; 613 + 614 + if (rela->sym->sec != insn->sec || 615 + rela->addend <= func->offset || 616 + rela->addend >= func->offset + func->len) 617 + break; 618 + 619 + alt_insn = find_insn(file, insn->sec, rela->addend); 620 + if (!alt_insn) { 621 + WARN("%s: can't find instruction at %s+0x%x", 622 + file->rodata->rela->name, insn->sec->name, 623 + rela->addend); 624 + return -1; 625 + } 626 + 627 + alt = malloc(sizeof(*alt)); 628 + if (!alt) { 629 + WARN("malloc failed"); 630 + return -1; 631 + } 632 + 633 + alt->insn = alt_insn; 634 + list_add_tail(&alt->list, &insn->alts); 635 + } 636 + 637 + return 0; 638 + } 639 + 640 + static int add_func_switch_tables(struct objtool_file *file, 641 + struct symbol *func) 642 + { 643 + struct instruction *insn, *prev_jump; 644 + struct rela *text_rela, *rodata_rela, *prev_rela; 645 + int ret; 646 + 647 + prev_jump = NULL; 648 + 649 + func_for_each_insn(file, func, insn) { 650 + if (insn->type != INSN_JUMP_DYNAMIC) 651 + continue; 652 + 653 + text_rela = find_rela_by_dest_range(insn->sec, insn->offset, 654 + insn->len); 655 + if (!text_rela || text_rela->sym != file->rodata->sym) 656 + continue; 657 + 658 + /* common case: jmpq *[addr](,%rax,8) */ 659 + rodata_rela = find_rela_by_dest(file->rodata, 660 + text_rela->addend); 661 + 662 + /* 663 + * TODO: Document where this is needed, or get rid of it. 664 + * 665 + * rare case: jmpq *[addr](%rip) 666 + */ 667 + if (!rodata_rela) 668 + rodata_rela = find_rela_by_dest(file->rodata, 669 + text_rela->addend + 4); 670 + 671 + if (!rodata_rela) 672 + continue; 673 + 674 + /* 675 + * We found a switch table, but we don't know yet how big it 676 + * is. Don't add it until we reach the end of the function or 677 + * the beginning of another switch table in the same function. 678 + */ 679 + if (prev_jump) { 680 + ret = add_switch_table(file, func, prev_jump, prev_rela, 681 + rodata_rela); 682 + if (ret) 683 + return ret; 684 + } 685 + 686 + prev_jump = insn; 687 + prev_rela = rodata_rela; 688 + } 689 + 690 + if (prev_jump) { 691 + ret = add_switch_table(file, func, prev_jump, prev_rela, NULL); 692 + if (ret) 693 + return ret; 694 + } 695 + 696 + return 0; 697 + } 698 + 603 699 /* 604 700 * For some switch statements, gcc generates a jump table in the .rodata 605 701 * section which contains a list of addresses within the function to jump to. ··· 704 606 */ 705 607 static int add_switch_table_alts(struct objtool_file *file) 706 608 { 707 - struct instruction *insn, *alt_insn; 708 - struct rela *rodata_rela, *text_rela; 709 - struct section *rodata; 609 + struct section *sec; 710 610 struct symbol *func; 711 - struct alternative *alt; 611 + int ret; 712 612 713 - for_each_insn(file, insn) { 714 - if (insn->type != INSN_JUMP_DYNAMIC) 715 - continue; 613 + if (!file->rodata || !file->rodata->rela) 614 + return 0; 716 615 717 - text_rela = find_rela_by_dest_range(insn->sec, insn->offset, 718 - insn->len); 719 - if (!text_rela || strcmp(text_rela->sym->name, ".rodata")) 720 - continue; 616 + list_for_each_entry(sec, &file->elf->sections, list) { 617 + list_for_each_entry(func, &sec->symbol_list, list) { 618 + if (func->type != STT_FUNC) 619 + continue; 721 620 722 - rodata = find_section_by_name(file->elf, ".rodata"); 723 - if (!rodata || !rodata->rela) 724 - continue; 725 - 726 - /* common case: jmpq *[addr](,%rax,8) */ 727 - rodata_rela = find_rela_by_dest(rodata, text_rela->addend); 728 - 729 - /* rare case: jmpq *[addr](%rip) */ 730 - if (!rodata_rela) 731 - rodata_rela = find_rela_by_dest(rodata, 732 - text_rela->addend + 4); 733 - if (!rodata_rela) 734 - continue; 735 - 736 - func = find_containing_func(insn->sec, insn->offset); 737 - if (!func) { 738 - WARN_FUNC("can't find containing func", 739 - insn->sec, insn->offset); 740 - return -1; 741 - } 742 - 743 - list_for_each_entry_from(rodata_rela, &rodata->rela->rela_list, 744 - list) { 745 - if (rodata_rela->sym->sec != insn->sec || 746 - rodata_rela->addend <= func->offset || 747 - rodata_rela->addend >= func->offset + func->len) 748 - break; 749 - 750 - alt_insn = find_insn(file, insn->sec, 751 - rodata_rela->addend); 752 - if (!alt_insn) { 753 - WARN("%s: can't find instruction at %s+0x%x", 754 - rodata->rela->name, insn->sec->name, 755 - rodata_rela->addend); 756 - return -1; 757 - } 758 - 759 - alt = malloc(sizeof(*alt)); 760 - if (!alt) { 761 - WARN("malloc failed"); 762 - return -1; 763 - } 764 - 765 - alt->insn = alt_insn; 766 - list_add_tail(&alt->list, &insn->alts); 621 + ret = add_func_switch_tables(file, func); 622 + if (ret) 623 + return ret; 767 624 } 768 625 } 769 626 ··· 728 675 static int decode_sections(struct objtool_file *file) 729 676 { 730 677 int ret; 678 + 679 + file->rodata = find_section_by_name(file->elf, ".rodata"); 731 680 732 681 ret = decode_instructions(file); 733 682 if (ret)