/*
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* objtool check:
*
* This command analyzes every .o file and ensures the validity of its stack
* trace metadata. It enforces a set of rules on asm code and C inline
* assembly code so that stack traces can be reliable.
*
* For more information, see tools/objtool/Documentation/stack-validation.txt.
*/
#include <string.h>
#include <subcmd/parse-options.h>
#include "builtin.h"
#include "elf.h"
#include "special.h"
#include "arch.h"
#include "warn.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define STATE_FP_SAVED 0x1
#define STATE_FP_SETUP 0x2
#define STATE_FENTRY 0x4
struct instruction {
struct list_head list;
struct section *sec;
unsigned long offset;
unsigned int len, state;
unsigned char type;
unsigned long immediate;
bool alt_group, visited;
struct symbol *call_dest;
struct instruction *jump_dest;
struct list_head alts;
};
struct alternative {
struct list_head list;
struct instruction *insn;
};
struct objtool_file {
struct elf *elf;
struct list_head insn_list;
struct section *rodata;
};
const char *objname;
static bool nofp;
static struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset)
{
struct instruction *insn;
list_for_each_entry(insn, &file->insn_list, list)
if (insn->sec == sec && insn->offset == offset)
return insn;
return NULL;
}
static struct instruction *next_insn_same_sec(struct objtool_file *file,
struct instruction *insn)
{
struct instruction *next = list_next_entry(insn, list);
if (&next->list == &file->insn_list || next->sec != insn->sec)
return NULL;
return next;
}
#define for_each_insn(file, insn) \
list_for_each_entry(insn, &file->insn_list, list)
#define func_for_each_insn(file, func, insn) \
for (insn = find_insn(file, func->sec, func->offset); \
insn && &insn->list != &file->insn_list && \
insn->sec == func->sec && \
insn->offset < func->offset + func->len; \
insn = list_next_entry(insn, list))
#define sec_for_each_insn_from(file, insn) \
for (; insn; insn = next_insn_same_sec(file, insn))
/*
* Check if the function has been manually whitelisted with the
* STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted
* due to its use of a context switching instruction.
*/
static bool ignore_func(struct objtool_file *file, struct symbol *func)
{
struct section *macro_sec;
struct rela *rela;
struct instruction *insn;
/* check for STACK_FRAME_NON_STANDARD */
macro_sec = find_section_by_name(file->elf, "__func_stack_frame_non_standard");
if (macro_sec && macro_sec->rela)
list_for_each_entry(rela, ¯o_sec->rela->rela_list, list)
if (rela->sym->sec == func->sec &&
rela->addend == func->offset)
return true;
/* check if it has a context switching instruction */
func_for_each_insn(file, func, insn)
if (insn->type == INSN_CONTEXT_SWITCH)
return true;
return false;
}
/*
* This checks to see if the given function is a "noreturn" function.
*
* For global functions which are outside the scope of this object file, we
* have to keep a manual list of them.
*
* For local functions, we have to detect them manually by simply looking for
* the lack of a return instruction.
*
* Returns:
* -1: error
* 0: no dead end
* 1: dead end
*/
static int __dead_end_function(struct objtool_file