@ -27,10 +27,6 @@
# include <linux/hashtable.h>
# include <linux/kernel.h>
# define STATE_FP_SAVED 0x1
# define STATE_FP_SETUP 0x2
# define STATE_FENTRY 0x4
struct alternative {
struct list_head list ;
struct instruction * insn ;
@ -38,6 +34,7 @@ struct alternative {
const char * objname ;
static bool nofp ;
struct cfi_state initial_func_cfi ;
static struct instruction * find_insn ( struct objtool_file * file ,
struct section * sec , unsigned long offset )
@ -56,7 +53,7 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file,
{
struct instruction * next = list_next_entry ( insn , list ) ;
if ( & next - > list = = & file - > insn_list | | next - > sec ! = insn - > sec )
if ( ! next | | & next - > list = = & file - > insn_list | | next - > sec ! = insn - > sec )
return NULL ;
return next ;
@ -67,7 +64,7 @@ static bool gcov_enabled(struct objtool_file *file)
struct section * sec ;
struct symbol * sym ;
list_for_each_entry ( sec , & file - > elf - > sections , list )
for_each_sec ( file , sec )
list_for_each_entry ( sym , & sec - > symbol_list , list )
if ( ! strncmp ( sym - > name , " __gcov_. " , 8 ) )
return true ;
@ -75,9 +72,6 @@ static bool gcov_enabled(struct objtool_file *file)
return false ;
}
# 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 & & \
@ -94,6 +88,9 @@ static bool gcov_enabled(struct objtool_file *file)
# define sec_for_each_insn_from(file, insn) \
for ( ; insn ; insn = next_insn_same_sec ( file , insn ) )
# define sec_for_each_insn_continue(file, insn) \
for ( insn = next_insn_same_sec ( file , insn ) ; insn ; \
insn = next_insn_same_sec ( file , insn ) )
/*
* Check if the function has been manually whitelisted with the
@ -103,7 +100,6 @@ static bool gcov_enabled(struct objtool_file *file)
static bool ignore_func ( struct objtool_file * file , struct symbol * func )
{
struct rela * rela ;
struct instruction * insn ;
/* check for STACK_FRAME_NON_STANDARD */
if ( file - > whitelist & & file - > whitelist - > rela )
@ -116,11 +112,6 @@ static bool ignore_func(struct objtool_file *file, struct symbol *func)
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 ;
}
@ -234,6 +225,17 @@ static int dead_end_function(struct objtool_file *file, struct symbol *func)
return __dead_end_function ( file , func , 0 ) ;
}
static void clear_insn_state ( struct insn_state * state )
{
int i ;
memset ( state , 0 , sizeof ( * state ) ) ;
state - > cfa . base = CFI_UNDEFINED ;
for ( i = 0 ; i < CFI_NUM_REGS ; i + + )
state - > regs [ i ] . base = CFI_UNDEFINED ;
state - > drap_reg = CFI_UNDEFINED ;
}
/*
* Call the arch - specific instruction decoder for all the instructions and add
* them to the global instruction list .
@ -246,23 +248,29 @@ static int decode_instructions(struct objtool_file *file)
struct instruction * insn ;
int ret ;
list_for_each_entry ( sec , & file - > elf - > sections , list ) {
for_each_sec ( file , sec ) {
if ( ! ( sec - > sh . sh_flags & SHF_EXECINSTR ) )
continue ;
for ( offset = 0 ; offset < sec - > len ; offset + = insn - > len ) {
insn = malloc ( sizeof ( * insn ) ) ;
if ( ! insn ) {
WARN ( " malloc failed " ) ;
return - 1 ;
}
memset ( insn , 0 , sizeof ( * insn ) ) ;
INIT_LIST_HEAD ( & insn - > alts ) ;
clear_insn_state ( & insn - > state ) ;
insn - > sec = sec ;
insn - > offset = offset ;
ret = arch_decode_instruction ( file - > elf , sec , offset ,
sec - > len - offset ,
& insn - > len , & insn - > type ,
& insn - > immediate ) ;
& insn - > immediate ,
& insn - > stack_op ) ;
if ( ret )
return ret ;
@ -352,7 +360,7 @@ static void add_ignores(struct objtool_file *file)
struct section * sec ;
struct symbol * func ;
list_for_each_entry ( sec , & file - > elf - > sections , list ) {
for_each_sec ( file , sec ) {
list_for_each_entry ( func , & sec - > symbol_list , list ) {
if ( func - > type ! = STT_FUNC )
continue ;
@ -361,7 +369,7 @@ static void add_ignores(struct objtool_file *file)
continue ;
func_for_each_insn ( file , func , insn )
insn - > visited = true ;
insn - > ignore = true ;
}
}
}
@ -381,8 +389,7 @@ static int add_jump_destinations(struct objtool_file *file)
insn - > type ! = INSN_JUMP_UNCONDITIONAL )
continue ;
/* skip ignores */
if ( insn - > visited )
if ( insn - > ignore )
continue ;
rela = find_rela_by_dest_range ( insn - > sec , insn - > offset ,
@ -519,10 +526,13 @@ static int handle_group_alt(struct objtool_file *file,
}
memset ( fake_jump , 0 , sizeof ( * fake_jump ) ) ;
INIT_LIST_HEAD ( & fake_jump - > alts ) ;
clear_insn_state ( & fake_jump - > state ) ;
fake_jump - > sec = special_alt - > new_sec ;
fake_jump - > offset = - 1 ;
fake_jump - > type = INSN_JUMP_UNCONDITIONAL ;
fake_jump - > jump_dest = list_next_entry ( last_orig_insn , list ) ;
fake_jump - > ignore = true ;
if ( ! special_alt - > new_len ) {
* new_insn = fake_jump ;
@ -844,7 +854,7 @@ static int add_switch_table_alts(struct objtool_file *file)
if ( ! file - > rodata | | ! file - > rodata - > rela )
return 0 ;
list_for_each_entry ( sec , & file - > elf - > sections , list ) {
for_each_sec ( file , sec ) {
list_for_each_entry ( func , & sec - > symbol_list , list ) {
if ( func - > type ! = STT_FUNC )
continue ;
@ -901,21 +911,423 @@ static bool is_fentry_call(struct instruction *insn)
return false ;
}
static bool has_modified_stack_frame ( struct instruction * insn )
static bool has_modified_stack_frame ( struct insn_state * state )
{
return ( insn - > state & STATE_FP_SAVED ) | |
( insn - > state & STATE_FP_SETUP ) ;
int i ;
if ( state - > cfa . base ! = initial_func_cfi . cfa . base | |
state - > cfa . offset ! = initial_func_cfi . cfa . offset | |
state - > stack_size ! = initial_func_cfi . cfa . offset | |
state - > drap )
return true ;
for ( i = 0 ; i < CFI_NUM_REGS ; i + + )
if ( state - > regs [ i ] . base ! = initial_func_cfi . regs [ i ] . base | |
state - > regs [ i ] . offset ! = initial_func_cfi . regs [ i ] . offset )
return true ;
return false ;
}
static bool has_valid_stack_frame ( struct insn_state * state )
{
if ( state - > cfa . base = = CFI_BP & & state - > regs [ CFI_BP ] . base = = CFI_CFA & &
state - > regs [ CFI_BP ] . offset = = - 16 )
return true ;
if ( state - > drap & & state - > regs [ CFI_BP ] . base = = CFI_BP )
return true ;
return false ;
}
static bool has_valid_stack_frame ( struct instruction * insn )
static void save_reg ( struct insn_state * state , unsigned char reg , int base ,
int offset )
{
return ( insn - > state & STATE_FP_SAVED ) & &
( insn - > state & STATE_FP_SETUP ) ;
if ( ( arch_callee_saved_reg ( reg ) | |
( state - > drap & & reg = = state - > drap_reg ) ) & &
state - > regs [ reg ] . base = = CFI_UNDEFINED ) {
state - > regs [ reg ] . base = base ;
state - > regs [ reg ] . offset = offset ;
}
}
static unsigned int frame_state ( unsigned long state )
static void restore_reg ( struct insn_state * state , unsigned char reg )
{
return ( state & ( STATE_FP_SAVED | STATE_FP_SETUP ) ) ;
state - > regs [ reg ] . base = CFI_UNDEFINED ;
state - > regs [ reg ] . offset = 0 ;
}
/*
* A note about DRAP stack alignment :
*
* GCC has the concept of a DRAP register , which is used to help keep track of
* the stack pointer when aligning the stack . r10 or r13 is used as the DRAP
* register . The typical DRAP pattern is :
*
* 4 c 8 d 54 24 08 lea 0x8 ( % rsp ) , % r10
* 48 83 e4 c0 and $ 0xffffffffffffffc0 , % rsp
* 41 ff 72 f8 pushq - 0x8 ( % r10 )
* 55 push % rbp
* 48 89 e5 mov % rsp , % rbp
* ( more pushes )
* 41 52 push % r10
* . . .
* 41 5 a pop % r10
* ( more pops )
* 5 d pop % rbp
* 49 8 d 62 f8 lea - 0x8 ( % r10 ) , % rsp
* c3 retq
*
* There are some variations in the epilogues , like :
*
* 5 b pop % rbx
* 41 5 a pop % r10
* 41 5 c pop % r12
* 41 5 d pop % r13
* 41 5 e pop % r14
* c9 leaveq
* 49 8 d 62 f8 lea - 0x8 ( % r10 ) , % rsp
* c3 retq
*
* and :
*
* 4 c 8 b 55 e8 mov - 0x18 ( % rbp ) , % r10
* 48 8 b 5 d e0 mov - 0x20 ( % rbp ) , % rbx
* 4 c 8 b 65 f0 mov - 0x10 ( % rbp ) , % r12
* 4 c 8 b 6 d f8 mov - 0x8 ( % rbp ) , % r13
* c9 leaveq
* 49 8 d 62 f8 lea - 0x8 ( % r10 ) , % rsp
* c3 retq
*
* Sometimes r13 is used as the DRAP register , in which case it ' s saved and
* restored beforehand :
*
* 41 55 push % r13
* 4 c 8 d 6 c 24 10 lea 0x10 ( % rsp ) , % r13
* 48 83 e4 f0 and $ 0xfffffffffffffff0 , % rsp
* . . .
* 49 8 d 65 f0 lea - 0x10 ( % r13 ) , % rsp
* 41 5 d pop % r13
* c3 retq
*/
static int update_insn_state ( struct instruction * insn , struct insn_state * state )
{
struct stack_op * op = & insn - > stack_op ;
struct cfi_reg * cfa = & state - > cfa ;
struct cfi_reg * regs = state - > regs ;
/* stack operations don't make sense with an undefined CFA */
if ( cfa - > base = = CFI_UNDEFINED ) {
if ( insn - > func ) {
WARN_FUNC ( " undefined stack state " , insn - > sec , insn - > offset ) ;
return - 1 ;
}
return 0 ;
}
switch ( op - > dest . type ) {
case OP_DEST_REG :
switch ( op - > src . type ) {
case OP_SRC_REG :
if ( cfa - > base = = op - > src . reg & & cfa - > base = = CFI_SP & &
op - > dest . reg = = CFI_BP & & regs [ CFI_BP ] . base = = CFI_CFA & &
regs [ CFI_BP ] . offset = = - cfa - > offset ) {
/* mov %rsp, %rbp */
cfa - > base = op - > dest . reg ;
state - > bp_scratch = false ;
} else if ( state - > drap ) {
/* drap: mov %rsp, %rbp */
regs [ CFI_BP ] . base = CFI_BP ;
regs [ CFI_BP ] . offset = - state - > stack_size ;
state - > bp_scratch = false ;
} else if ( ! nofp ) {
WARN_FUNC ( " unknown stack-related register move " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
break ;
case OP_SRC_ADD :
if ( op - > dest . reg = = CFI_SP & & op - > src . reg = = CFI_SP ) {
/* add imm, %rsp */
state - > stack_size - = op - > src . offset ;
if ( cfa - > base = = CFI_SP )
cfa - > offset - = op - > src . offset ;
break ;
}
if ( op - > dest . reg = = CFI_SP & & op - > src . reg = = CFI_BP ) {
/* lea disp(%rbp), %rsp */
state - > stack_size = - ( op - > src . offset + regs [ CFI_BP ] . offset ) ;
break ;
}
if ( op - > dest . reg ! = CFI_BP & & op - > src . reg = = CFI_SP & &
cfa - > base = = CFI_SP ) {
/* drap: lea disp(%rsp), %drap */
state - > drap_reg = op - > dest . reg ;
break ;
}
if ( state - > drap & & op - > dest . reg = = CFI_SP & &
op - > src . reg = = state - > drap_reg ) {
/* drap: lea disp(%drap), %rsp */
cfa - > base = CFI_SP ;
cfa - > offset = state - > stack_size = - op - > src . offset ;
state - > drap_reg = CFI_UNDEFINED ;
state - > drap = false ;
break ;
}
if ( op - > dest . reg = = state - > cfa . base ) {
WARN_FUNC ( " unsupported stack register modification " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
break ;
case OP_SRC_AND :
if ( op - > dest . reg ! = CFI_SP | |
( state - > drap_reg ! = CFI_UNDEFINED & & cfa - > base ! = CFI_SP ) | |
( state - > drap_reg = = CFI_UNDEFINED & & cfa - > base ! = CFI_BP ) ) {
WARN_FUNC ( " unsupported stack pointer realignment " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
if ( state - > drap_reg ! = CFI_UNDEFINED ) {
/* drap: and imm, %rsp */
cfa - > base = state - > drap_reg ;
cfa - > offset = state - > stack_size = 0 ;
state - > drap = true ;
}
/*
* Older versions of GCC ( 4.8 ish ) realign the stack
* without DRAP , with a frame pointer .
*/
break ;
case OP_SRC_POP :
if ( ! state - > drap & & op - > dest . type = = OP_DEST_REG & &
op - > dest . reg = = cfa - > base ) {
/* pop %rbp */
cfa - > base = CFI_SP ;
}
if ( regs [ op - > dest . reg ] . offset = = - state - > stack_size ) {
if ( state - > drap & & cfa - > base = = CFI_BP_INDIRECT & &
op - > dest . type = = OP_DEST_REG & &
op - > dest . reg = = state - > drap_reg ) {
/* drap: pop %drap */
cfa - > base = state - > drap_reg ;
cfa - > offset = 0 ;
}
restore_reg ( state , op - > dest . reg ) ;
}
state - > stack_size - = 8 ;
if ( cfa - > base = = CFI_SP )
cfa - > offset - = 8 ;
break ;
case OP_SRC_REG_INDIRECT :
if ( state - > drap & & op - > src . reg = = CFI_BP & &
op - > src . offset = = regs [ op - > dest . reg ] . offset ) {
/* drap: mov disp(%rbp), %reg */
if ( op - > dest . reg = = state - > drap_reg ) {
cfa - > base = state - > drap_reg ;
cfa - > offset = 0 ;
}
restore_reg ( state , op - > dest . reg ) ;
} else if ( op - > src . reg = = cfa - > base & &
op - > src . offset = = regs [ op - > dest . reg ] . offset + cfa - > offset ) {
/* mov disp(%rbp), %reg */
/* mov disp(%rsp), %reg */
restore_reg ( state , op - > dest . reg ) ;
}
break ;
default :
WARN_FUNC ( " unknown stack-related instruction " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
break ;
case OP_DEST_PUSH :
state - > stack_size + = 8 ;
if ( cfa - > base = = CFI_SP )
cfa - > offset + = 8 ;
if ( op - > src . type ! = OP_SRC_REG )
break ;
if ( state - > drap ) {
if ( op - > src . reg = = cfa - > base & & op - > src . reg = = state - > drap_reg ) {
/* drap: push %drap */
cfa - > base = CFI_BP_INDIRECT ;
cfa - > offset = - state - > stack_size ;
/* save drap so we know when to undefine it */
save_reg ( state , op - > src . reg , CFI_CFA , - state - > stack_size ) ;
} else if ( op - > src . reg = = CFI_BP & & cfa - > base = = state - > drap_reg ) {
/* drap: push %rbp */
state - > stack_size = 0 ;
} else if ( regs [ op - > src . reg ] . base = = CFI_UNDEFINED ) {
/* drap: push %reg */
save_reg ( state , op - > src . reg , CFI_BP , - state - > stack_size ) ;
}
} else {
/* push %reg */
save_reg ( state , op - > src . reg , CFI_CFA , - state - > stack_size ) ;
}
/* detect when asm code uses rbp as a scratch register */
if ( ! nofp & & insn - > func & & op - > src . reg = = CFI_BP & &
cfa - > base ! = CFI_BP )
state - > bp_scratch = true ;
break ;
case OP_DEST_REG_INDIRECT :
if ( state - > drap ) {
if ( op - > src . reg = = cfa - > base & & op - > src . reg = = state - > drap_reg ) {
/* drap: mov %drap, disp(%rbp) */
cfa - > base = CFI_BP_INDIRECT ;
cfa - > offset = op - > dest . offset ;
/* save drap so we know when to undefine it */
save_reg ( state , op - > src . reg , CFI_CFA , op - > dest . offset ) ;
}
else if ( regs [ op - > src . reg ] . base = = CFI_UNDEFINED ) {
/* drap: mov reg, disp(%rbp) */
save_reg ( state , op - > src . reg , CFI_BP , op - > dest . offset ) ;
}
} else if ( op - > dest . reg = = cfa - > base ) {
/* mov reg, disp(%rbp) */
/* mov reg, disp(%rsp) */
save_reg ( state , op - > src . reg , CFI_CFA ,
op - > dest . offset - state - > cfa . offset ) ;
}
break ;
case OP_DEST_LEAVE :
if ( ( ! state - > drap & & cfa - > base ! = CFI_BP ) | |
( state - > drap & & cfa - > base ! = state - > drap_reg ) ) {
WARN_FUNC ( " leave instruction with modified stack frame " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
/* leave (mov %rbp, %rsp; pop %rbp) */
state - > stack_size = - state - > regs [ CFI_BP ] . offset - 8 ;
restore_reg ( state , CFI_BP ) ;
if ( ! state - > drap ) {
cfa - > base = CFI_SP ;
cfa - > offset - = 8 ;
}
break ;
case OP_DEST_MEM :
if ( op - > src . type ! = OP_SRC_POP ) {
WARN_FUNC ( " unknown stack-related memory operation " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
/* pop mem */
state - > stack_size - = 8 ;
if ( cfa - > base = = CFI_SP )
cfa - > offset - = 8 ;
break ;
default :
WARN_FUNC ( " unknown stack-related instruction " ,
insn - > sec , insn - > offset ) ;
return - 1 ;
}
return 0 ;
}
static bool insn_state_match ( struct instruction * insn , struct insn_state * state )
{
struct insn_state * state1 = & insn - > state , * state2 = state ;
int i ;
if ( memcmp ( & state1 - > cfa , & state2 - > cfa , sizeof ( state1 - > cfa ) ) ) {
WARN_FUNC ( " stack state mismatch: cfa1=%d%+d cfa2=%d%+d " ,
insn - > sec , insn - > offset ,
state1 - > cfa . base , state1 - > cfa . offset ,
state2 - > cfa . base , state2 - > cfa . offset ) ;
} else if ( memcmp ( & state1 - > regs , & state2 - > regs , sizeof ( state1 - > regs ) ) ) {
for ( i = 0 ; i < CFI_NUM_REGS ; i + + ) {
if ( ! memcmp ( & state1 - > regs [ i ] , & state2 - > regs [ i ] ,
sizeof ( struct cfi_reg ) ) )
continue ;
WARN_FUNC ( " stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d " ,
insn - > sec , insn - > offset ,
i , state1 - > regs [ i ] . base , state1 - > regs [ i ] . offset ,
i , state2 - > regs [ i ] . base , state2 - > regs [ i ] . offset ) ;
break ;
}
} else if ( state1 - > drap ! = state2 - > drap | |
( state1 - > drap & & state1 - > drap_reg ! = state2 - > drap_reg ) ) {
WARN_FUNC ( " stack state mismatch: drap1=%d(%d) drap2=%d(%d) " ,
insn - > sec , insn - > offset ,
state1 - > drap , state1 - > drap_reg ,
state2 - > drap , state2 - > drap_reg ) ;
} else
return true ;
return false ;
}
/*
@ -924,24 +1336,22 @@ static unsigned int frame_state(unsigned long state)
* each instruction and validate all the rules described in
* tools / objtool / Documentation / stack - validation . txt .
*/
static int validate_branch ( struct objtool_file * file ,
struct instruction * first , unsigned char first_ state )
static int validate_branch ( struct objtool_file * file , struct instruction * first ,
struct insn_state state )
{
struct alternative * alt ;
struct instruction * insn ;
struct section * sec ;
struct symbol * func = NULL ;
unsigned char state ;
int ret ;
insn = first ;
sec = insn - > sec ;
state = first_state ;
if ( insn - > alt_group & & list_empty ( & insn - > alts ) ) {
WARN_FUNC ( " don't know how to handle branch to middle of alternative instruction group " ,
sec , insn - > offset ) ;
return 1 ;
return - 1 ;
}
while ( 1 ) {
@ -951,23 +1361,21 @@ static int validate_branch(struct objtool_file *file,
func - > name , insn - > func - > name ) ;
return 1 ;
}
func = insn - > func ;
}
func = insn - > func ;
if ( insn - > visited ) {
if ( frame_state ( insn - > state ) ! = frame_state ( state ) ) {
WARN_FUNC ( " frame pointer state mismatch " ,
sec , insn - > offset ) ;
if ( ! ! insn_state_match ( insn , & state ) )
return 1 ;
}
return 0 ;
}
insn - > visited = true ;
insn - > state = state ;
insn - > visited = true ;
list_for_each_entry ( alt , & insn - > alts , list ) {
ret = validate_branch ( file , alt - > insn , state ) ;
if ( ret )
@ -976,50 +1384,24 @@ static int validate_branch(struct objtool_file *file,
switch ( insn - > type ) {
case INSN_FP_SAVE :
if ( ! nofp ) {
if ( state & STATE_FP_SAVED ) {
WARN_FUNC ( " duplicate frame pointer save " ,
sec , insn - > offset ) ;
return 1 ;
}
state | = STATE_FP_SAVED ;
}
break ;
case INSN_FP_SETUP :
if ( ! nofp ) {
if ( state & STATE_FP_SETUP ) {
WARN_FUNC ( " duplicate frame pointer setup " ,
sec , insn - > offset ) ;
return 1 ;
}
state | = STATE_FP_SETUP ;
}
break ;
case INSN_FP_RESTORE :
if ( ! nofp ) {
if ( has_valid_stack_frame ( insn ) )
state & = ~ STATE_FP_SETUP ;
state & = ~ STATE_FP_SAVED ;
}
break ;
case INSN_RETURN :
if ( ! nofp & & has_modified_stack_frame ( insn ) ) {
WARN_FUNC ( " return without frame pointer restor e " ,
if ( func & & has_modified_stack_frame ( & state ) ) {
WARN_FUNC ( " return with modified stack frame " ,
sec , insn - > offset ) ;
return 1 ;
}
if ( state . bp_scratch ) {
WARN ( " %s uses BP as a scratch register " ,
insn - > func - > name ) ;
return 1 ;
}
return 0 ;
case INSN_CALL :
if ( is_fentry_call ( insn ) ) {
state | = STATE_FENTRY ;
if ( is_fentry_call ( insn ) )
break ;
}
ret = dead_end_function ( file , insn - > call_dest ) ;
if ( ret = = 1 )
@ -1029,7 +1411,7 @@ static int validate_branch(struct objtool_file *file,
/* fallthrough */
case INSN_CALL_DYNAMIC :
if ( ! nofp & & ! has_valid_stack_frame ( insn ) ) {
if ( ! nofp & & func & & ! has_valid_stack_frame ( & state ) ) {
WARN_FUNC ( " call without frame pointer save/setup " ,
sec , insn - > offset ) ;
return 1 ;
@ -1043,8 +1425,8 @@ static int validate_branch(struct objtool_file *file,
state ) ;
if ( ret )
return 1 ;
} else if ( has_modified_stack_frame ( insn ) ) {
WARN_FUNC ( " sibling call from callable instruction with changed frame pointer " ,
} else if ( func & & has_modified_stack_frame ( & state ) ) {
WARN_FUNC ( " sibling call from callable instruction with modified stack frame " ,
sec , insn - > offset ) ;
return 1 ;
} /* else it's a sibling call */
@ -1055,15 +1437,29 @@ static int validate_branch(struct objtool_file *file,
break ;
case INSN_JUMP_DYNAMIC :
if ( list_empty ( & insn - > alts ) & &
has_modified_stack_frame ( insn ) ) {
WARN_FUNC ( " sibling call from callable instruction with changed frame pointer " ,
if ( func & & list_empty ( & insn - > alts ) & &
has_modified_stack_frame ( & state ) ) {
WARN_FUNC ( " sibling call from callable instruction with modified stack frame " ,
sec , insn - > offset ) ;
return 1 ;
}
return 0 ;
case INSN_CONTEXT_SWITCH :
if ( func ) {
WARN_FUNC ( " unsupported instruction in callable function " ,
sec , insn - > offset ) ;
return 1 ;
}
return 0 ;
case INSN_STACK :
if ( update_insn_state ( insn , & state ) )
return - 1 ;
break ;
default :
break ;
}
@ -1094,12 +1490,18 @@ static bool is_ubsan_insn(struct instruction *insn)
" __ubsan_handle_builtin_unreachable " ) ) ;
}
static bool ignore_unreachable_insn ( struct symbol * func ,
struct instruction * insn )
static bool ignore_unreachable_insn ( struct instruction * insn )
{
int i ;
if ( insn - > type = = INSN_NOP )
if ( insn - > ignore | | insn - > type = = INSN_NOP )
return true ;
/*
* Ignore any unused exceptions . This can happen when a whitelisted
* function has an exception table entry .
*/
if ( ! strcmp ( insn - > sec - > name , " .fixup " ) )
return true ;
/*
@ -1108,6 +1510,8 @@ static bool ignore_unreachable_insn(struct symbol *func,
*
* End the search at 5 instructions to avoid going into the weeds .
*/
if ( ! insn - > func )
return false ;
for ( i = 0 ; i < 5 ; i + + ) {
if ( is_kasan_insn ( insn ) | | is_ubsan_insn ( insn ) )
@ -1118,7 +1522,7 @@ static bool ignore_unreachable_insn(struct symbol *func,
continue ;
}
if ( insn - > offset + insn - > len > = func - > offset + func - > len )
if ( insn - > offset + insn - > len > = insn - > func - > offset + insn - > func - > len )
break ;
insn = list_next_entry ( insn , list ) ;
}
@ -1131,73 +1535,58 @@ static int validate_functions(struct objtool_file *file)
struct section * sec ;
struct symbol * func ;
struct instruction * insn ;
struct insn_state state ;
int ret , warnings = 0 ;
list_for_each_entry ( sec , & file - > elf - > sections , list ) {
clear_insn_state ( & state ) ;
state . cfa = initial_func_cfi . cfa ;
memcpy ( & state . regs , & initial_func_cfi . regs ,
CFI_NUM_REGS * sizeof ( struct cfi_reg ) ) ;
state . stack_size = initial_func_cfi . cfa . offset ;
for_each_sec ( file , sec ) {
list_for_each_entry ( func , & sec - > symbol_list , list ) {
if ( func - > type ! = STT_FUNC )
continue ;
insn = find_insn ( file , sec , func - > offset ) ;
if ( ! insn )
if ( ! insn | | insn - > ignore )
continue ;
ret = validate_branch ( file , insn , 0 ) ;
ret = validate_branch ( file , insn , state ) ;
warnings + = ret ;
}
}
list_for_each_entry ( sec , & file - > elf - > sections , list ) {
list_for_each_entry ( func , & sec - > symbol_list , list ) {
if ( func - > type ! = STT_FUNC )
continue ;
func_for_each_insn ( file , func , insn ) {
if ( insn - > visited )
continue ;
insn - > visited = true ;
if ( file - > ignore_unreachables | | warnings | |
ignore_unreachable_insn ( func , insn ) )
continue ;
/*
* gcov produces a lot of unreachable
* instructions . If we get an unreachable
* warning and the file has gcov enabled , just
* ignore it , and all other such warnings for
* the file .
*/
if ( ! file - > ignore_unreachables & &
gcov_enabled ( file ) ) {
file - > ignore_unreachables = true ;
continue ;
}
WARN_FUNC ( " function has unreachable instruction " , insn - > sec , insn - > offset ) ;
warnings + + ;
}
}
}
return warnings ;
}
static int validate_uncall able_instructions ( struct objtool_file * file )
static int validate_reachable_instructions ( struct objtool_file * file )
{
struct instruction * insn ;
int warnings = 0 ;
if ( file - > ignore_unreachables )
return 0 ;
for_each_insn ( file , insn ) {
if ( ! insn - > visited & & insn - > type = = INSN_RETURN ) {
WARN_FUNC ( " return instruction outside of a callable function " ,
insn - > sec , insn - > offset ) ;
warnings + + ;
}
if ( insn - > visited | | ignore_unreachable_insn ( insn ) )
continue ;
/*
* gcov produces a lot of unreachable instructions . If we get
* an unreachable warning and the file has gcov enabled , just
* ignore it , and all other such warnings for the file . Do
* this here because this is an expensive function .
*/
if ( gcov_enabled ( file ) )
return 0 ;
WARN_FUNC ( " unreachable instruction " , insn - > sec , insn - > offset ) ;
return 1 ;
}
return warnings ;
return 0 ;
}
static void cleanup ( struct objtool_file * file )
@ -1226,10 +1615,8 @@ int check(const char *_objname, bool _nofp)
nofp = _nofp ;
file . elf = elf_open ( objname ) ;
if ( ! file . elf ) {
fprintf ( stderr , " error reading elf file %s \n " , objname ) ;
if ( ! file . elf )
return 1 ;
}
INIT_LIST_HEAD ( & file . insn_list ) ;
hash_init ( file . insn_hash ) ;
@ -1238,21 +1625,28 @@ int check(const char *_objname, bool _nofp)
file . ignore_unreachables = false ;
file . c_file = find_section_by_name ( file . elf , " .comment " ) ;
arch_initial_func_cfi_state ( & initial_func_cfi ) ;
ret = decode_sections ( & file ) ;
if ( ret < 0 )
goto out ;
warnings + = ret ;
ret = validate_functions ( & file ) ;
if ( ret < 0 )
if ( list_empty ( & file . insn_list ) )
goto out ;
warnings + = ret ;
ret = validate_uncallable_instru ctions ( & file ) ;
ret = validate_f unctions ( & file ) ;
if ( ret < 0 )
goto out ;
warnings + = ret ;
if ( ! warnings ) {
ret = validate_reachable_instructions ( & file ) ;
if ( ret < 0 )
goto out ;
warnings + = ret ;
}
out :
cleanup ( & file ) ;