2019-05-19 16:51:43 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2017-07-11 18:33:42 +03:00
/*
* Copyright ( C ) 2017 Josh Poimboeuf < jpoimboe @ redhat . com >
*/
# include <stdlib.h>
# include <string.h>
# include "check.h"
# include "warn.h"
int create_orc ( struct objtool_file * file )
{
struct instruction * insn ;
for_each_insn ( file , insn ) {
struct orc_entry * orc = & insn - > orc ;
2020-03-25 16:04:45 +03:00
struct cfi_reg * cfa = & insn - > cfi . cfa ;
struct cfi_reg * bp = & insn - > cfi . regs [ CFI_BP ] ;
2017-07-11 18:33:42 +03:00
2020-03-25 16:04:45 +03:00
orc - > end = insn - > cfi . end ;
2018-05-18 09:47:12 +03:00
2017-07-11 18:33:42 +03:00
if ( cfa - > base = = CFI_UNDEFINED ) {
orc - > sp_reg = ORC_REG_UNDEFINED ;
continue ;
}
switch ( cfa - > base ) {
case CFI_SP :
orc - > sp_reg = ORC_REG_SP ;
break ;
case CFI_SP_INDIRECT :
orc - > sp_reg = ORC_REG_SP_INDIRECT ;
break ;
case CFI_BP :
orc - > sp_reg = ORC_REG_BP ;
break ;
case CFI_BP_INDIRECT :
orc - > sp_reg = ORC_REG_BP_INDIRECT ;
break ;
case CFI_R10 :
orc - > sp_reg = ORC_REG_R10 ;
break ;
case CFI_R13 :
orc - > sp_reg = ORC_REG_R13 ;
break ;
case CFI_DI :
orc - > sp_reg = ORC_REG_DI ;
break ;
case CFI_DX :
orc - > sp_reg = ORC_REG_DX ;
break ;
default :
WARN_FUNC ( " unknown CFA base reg %d " ,
insn - > sec , insn - > offset , cfa - > base ) ;
return - 1 ;
}
switch ( bp - > base ) {
case CFI_UNDEFINED :
orc - > bp_reg = ORC_REG_UNDEFINED ;
break ;
case CFI_CFA :
orc - > bp_reg = ORC_REG_PREV_SP ;
break ;
case CFI_BP :
orc - > bp_reg = ORC_REG_BP ;
break ;
default :
WARN_FUNC ( " unknown BP base reg %d " ,
insn - > sec , insn - > offset , bp - > base ) ;
return - 1 ;
}
orc - > sp_offset = cfa - > offset ;
orc - > bp_offset = bp - > offset ;
2020-03-25 16:04:45 +03:00
orc - > type = insn - > cfi . type ;
2017-07-11 18:33:42 +03:00
}
return 0 ;
}
2020-03-12 13:23:36 +03:00
static int create_orc_entry ( struct elf * elf , struct section * u_sec , struct section * ip_relasec ,
2017-07-11 18:33:42 +03:00
unsigned int idx , struct section * insn_sec ,
unsigned long insn_off , struct orc_entry * o )
{
struct orc_entry * orc ;
struct rela * rela ;
/* populate ORC data */
orc = ( struct orc_entry * ) u_sec - > data - > d_buf + idx ;
memcpy ( orc , o , sizeof ( * orc ) ) ;
/* populate rela for ip */
rela = malloc ( sizeof ( * rela ) ) ;
if ( ! rela ) {
perror ( " malloc " ) ;
return - 1 ;
}
memset ( rela , 0 , sizeof ( * rela ) ) ;
2020-04-01 21:23:27 +03:00
if ( insn_sec - > sym ) {
rela - > sym = insn_sec - > sym ;
rela - > addend = insn_off ;
} else {
/*
* The Clang assembler doesn ' t produce section symbols , so we
* have to reference the function symbol instead :
*/
rela - > sym = find_symbol_containing ( insn_sec , insn_off ) ;
if ( ! rela - > sym ) {
/*
* Hack alert . This happens when we need to reference
* the NOP pad insn immediately after the function .
*/
rela - > sym = find_symbol_containing ( insn_sec ,
insn_off - 1 ) ;
}
if ( ! rela - > sym ) {
WARN ( " missing symbol for insn at offset 0x%lx \n " ,
insn_off ) ;
return - 1 ;
}
rela - > addend = insn_off - rela - > sym - > offset ;
}
2017-07-11 18:33:42 +03:00
rela - > type = R_X86_64_PC32 ;
rela - > offset = idx * sizeof ( int ) ;
2020-03-12 13:23:36 +03:00
rela - > sec = ip_relasec ;
2017-07-11 18:33:42 +03:00
2020-03-12 16:29:38 +03:00
elf_add_rela ( elf , rela ) ;
2017-07-11 18:33:42 +03:00
return 0 ;
}
int create_orc_sections ( struct objtool_file * file )
{
struct instruction * insn , * prev_insn ;
struct section * sec , * u_sec , * ip_relasec ;
unsigned int idx ;
struct orc_entry empty = {
. sp_reg = ORC_REG_UNDEFINED ,
. bp_reg = ORC_REG_UNDEFINED ,
. type = ORC_TYPE_CALL ,
} ;
sec = find_section_by_name ( file - > elf , " .orc_unwind " ) ;
if ( sec ) {
WARN ( " file already has .orc_unwind section, skipping " ) ;
return - 1 ;
}
/* count the number of needed orcs */
idx = 0 ;
for_each_sec ( file , sec ) {
if ( ! sec - > text )
continue ;
prev_insn = NULL ;
sec_for_each_insn ( file , sec , insn ) {
if ( ! prev_insn | |
memcmp ( & insn - > orc , & prev_insn - > orc ,
sizeof ( struct orc_entry ) ) ) {
idx + + ;
}
prev_insn = insn ;
}
/* section terminator */
if ( prev_insn )
idx + + ;
}
if ( ! idx )
return - 1 ;
/* create .orc_unwind_ip and .rela.orc_unwind_ip sections */
sec = elf_create_section ( file - > elf , " .orc_unwind_ip " , sizeof ( int ) , idx ) ;
2017-12-30 23:43:32 +03:00
if ( ! sec )
return - 1 ;
2017-07-11 18:33:42 +03:00
ip_relasec = elf_create_rela_section ( file - > elf , sec ) ;
if ( ! ip_relasec )
return - 1 ;
/* create .orc_unwind section */
u_sec = elf_create_section ( file - > elf , " .orc_unwind " ,
sizeof ( struct orc_entry ) , idx ) ;
/* populate sections */
idx = 0 ;
for_each_sec ( file , sec ) {
if ( ! sec - > text )
continue ;
prev_insn = NULL ;
sec_for_each_insn ( file , sec , insn ) {
if ( ! prev_insn | | memcmp ( & insn - > orc , & prev_insn - > orc ,
sizeof ( struct orc_entry ) ) ) {
2020-03-12 13:23:36 +03:00
if ( create_orc_entry ( file - > elf , u_sec , ip_relasec , idx ,
2017-07-11 18:33:42 +03:00
insn - > sec , insn - > offset ,
& insn - > orc ) )
return - 1 ;
idx + + ;
}
prev_insn = insn ;
}
/* section terminator */
if ( prev_insn ) {
2020-03-12 13:23:36 +03:00
if ( create_orc_entry ( file - > elf , u_sec , ip_relasec , idx ,
2017-07-11 18:33:42 +03:00
prev_insn - > sec ,
prev_insn - > offset + prev_insn - > len ,
& empty ) )
return - 1 ;
idx + + ;
}
}
if ( elf_rebuild_rela_section ( ip_relasec ) )
return - 1 ;
return 0 ;
}