2012-08-07 17:20:46 +04:00
/*
* Post mortem Dwarf CFI based unwinding on top of regs and stack dumps .
*
* Lots of this code have been borrowed or heavily inspired from parts of
* the libunwind 0.99 code which are ( amongst other contributors I may have
* forgotten ) :
*
* Copyright ( C ) 2002 - 2007 Hewlett - Packard Co
* Contributed by David Mosberger - Tang < davidm @ hpl . hp . com >
*
* And the bugs have been added by :
*
* Copyright ( C ) 2010 , Frederic Weisbecker < fweisbec @ gmail . com >
* Copyright ( C ) 2012 , Jiri Olsa < jolsa @ redhat . com >
*
*/
# include <elf.h>
# include <gelf.h>
# include <fcntl.h>
# include <string.h>
# include <unistd.h>
# include <sys/mman.h>
# include <linux/list.h>
# include <libunwind.h>
# include <libunwind-ptrace.h>
# include "thread.h"
# include "session.h"
# include "perf_regs.h"
# include "unwind.h"
2014-01-16 13:39:49 +04:00
# include "symbol.h"
2012-08-07 17:20:46 +04:00
# include "util.h"
extern int
UNW_OBJ ( dwarf_search_unwind_table ) ( unw_addr_space_t as ,
unw_word_t ip ,
unw_dyn_info_t * di ,
unw_proc_info_t * pi ,
int need_unwind_info , void * arg ) ;
# define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
2013-09-26 15:36:38 +04:00
extern int
UNW_OBJ ( dwarf_find_debug_frame ) ( int found , unw_dyn_info_t * di_debug ,
unw_word_t ip ,
unw_word_t segbase ,
const char * obj_name , unw_word_t start ,
unw_word_t end ) ;
# define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
2012-08-07 17:20:46 +04:00
# define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */
# define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */
/* Pointer-encoding formats: */
# define DW_EH_PE_omit 0xff
# define DW_EH_PE_ptr 0x00 /* pointer-sized unsigned value */
# define DW_EH_PE_udata4 0x03 /* unsigned 32-bit value */
# define DW_EH_PE_udata8 0x04 /* unsigned 64-bit value */
# define DW_EH_PE_sdata4 0x0b /* signed 32-bit value */
# define DW_EH_PE_sdata8 0x0c /* signed 64-bit value */
/* Pointer-encoding application: */
# define DW_EH_PE_absptr 0x00 /* absolute value */
# define DW_EH_PE_pcrel 0x10 /* rel. to addr. of encoded value */
/*
* The following are not documented by LSB v1 .3 , yet they are used by
* GCC , presumably they aren ' t documented by LSB since they aren ' t
* used on Linux :
*/
# define DW_EH_PE_funcrel 0x40 /* start-of-procedure-relative */
# define DW_EH_PE_aligned 0x50 /* aligned pointer */
/* Flags intentionaly not handled, since they're not needed:
* # define DW_EH_PE_indirect 0x80
* # define DW_EH_PE_uleb128 0x01
* # define DW_EH_PE_udata2 0x02
* # define DW_EH_PE_sleb128 0x09
* # define DW_EH_PE_sdata2 0x0a
* # define DW_EH_PE_textrel 0x20
* # define DW_EH_PE_datarel 0x30
*/
struct unwind_info {
struct perf_sample * sample ;
struct machine * machine ;
struct thread * thread ;
u64 sample_uregs ;
} ;
# define dw_read(ptr, type, end) ({ \
type * __p = ( type * ) ptr ; \
type __v ; \
if ( ( __p + 1 ) > ( type * ) end ) \
return - EINVAL ; \
__v = * __p + + ; \
ptr = ( typeof ( ptr ) ) __p ; \
__v ; \
} )
static int __dw_read_encoded_value ( u8 * * p , u8 * end , u64 * val ,
u8 encoding )
{
u8 * cur = * p ;
* val = 0 ;
switch ( encoding ) {
case DW_EH_PE_omit :
* val = 0 ;
goto out ;
case DW_EH_PE_ptr :
* val = dw_read ( cur , unsigned long , end ) ;
goto out ;
default :
break ;
}
switch ( encoding & DW_EH_PE_APPL_MASK ) {
case DW_EH_PE_absptr :
break ;
case DW_EH_PE_pcrel :
* val = ( unsigned long ) cur ;
break ;
default :
return - EINVAL ;
}
if ( ( encoding & 0x07 ) = = 0x00 )
encoding | = DW_EH_PE_udata4 ;
switch ( encoding & DW_EH_PE_FORMAT_MASK ) {
case DW_EH_PE_sdata4 :
* val + = dw_read ( cur , s32 , end ) ;
break ;
case DW_EH_PE_udata4 :
* val + = dw_read ( cur , u32 , end ) ;
break ;
case DW_EH_PE_sdata8 :
* val + = dw_read ( cur , s64 , end ) ;
break ;
case DW_EH_PE_udata8 :
* val + = dw_read ( cur , u64 , end ) ;
break ;
default :
return - EINVAL ;
}
out :
* p = cur ;
return 0 ;
}
# define dw_read_encoded_value(ptr, end, enc) ({ \
u64 __v ; \
if ( __dw_read_encoded_value ( & ptr , end , & __v , enc ) ) { \
return - EINVAL ; \
} \
__v ; \
} )
static u64 elf_section_offset ( int fd , const char * name )
{
Elf * elf ;
GElf_Ehdr ehdr ;
GElf_Shdr shdr ;
u64 offset = 0 ;
elf = elf_begin ( fd , PERF_ELF_C_READ_MMAP , NULL ) ;
if ( elf = = NULL )
return 0 ;
do {
if ( gelf_getehdr ( elf , & ehdr ) = = NULL )
break ;
2014-01-16 13:39:49 +04:00
if ( ! elf_section_by_name ( elf , & ehdr , & shdr , name , NULL ) )
2012-08-07 17:20:46 +04:00
break ;
offset = shdr . sh_offset ;
} while ( 0 ) ;
elf_end ( elf ) ;
return offset ;
}
struct table_entry {
u32 start_ip_offset ;
u32 fde_offset ;
} ;
struct eh_frame_hdr {
unsigned char version ;
unsigned char eh_frame_ptr_enc ;
unsigned char fde_count_enc ;
unsigned char table_enc ;
/*
* The rest of the header is variable - length and consists of the
* following members :
*
* encoded_t eh_frame_ptr ;
* encoded_t fde_count ;
*/
/* A single encoded pointer should not be more than 8 bytes. */
u64 enc [ 2 ] ;
/*
* struct {
* encoded_t start_ip ;
* encoded_t fde_addr ;
* } binary_search_table [ fde_count ] ;
*/
char data [ 0 ] ;
} __packed ;
static int unwind_spec_ehframe ( struct dso * dso , struct machine * machine ,
u64 offset , u64 * table_data , u64 * segbase ,
u64 * fde_count )
{
struct eh_frame_hdr hdr ;
u8 * enc = ( u8 * ) & hdr . enc ;
u8 * end = ( u8 * ) & hdr . data ;
ssize_t r ;
r = dso__data_read_offset ( dso , machine , offset ,
( u8 * ) & hdr , sizeof ( hdr ) ) ;
if ( r ! = sizeof ( hdr ) )
return - EINVAL ;
/* We dont need eh_frame_ptr, just skip it. */
dw_read_encoded_value ( enc , end , hdr . eh_frame_ptr_enc ) ;
* fde_count = dw_read_encoded_value ( enc , end , hdr . fde_count_enc ) ;
* segbase = offset ;
* table_data = ( enc - ( u8 * ) & hdr ) + offset ;
return 0 ;
}
2013-09-26 15:36:38 +04:00
static int read_unwind_spec_eh_frame ( struct dso * dso , struct machine * machine ,
u64 * table_data , u64 * segbase ,
u64 * fde_count )
2012-08-07 17:20:46 +04:00
{
int ret = - EINVAL , fd ;
u64 offset ;
fd = dso__data_fd ( dso , machine ) ;
if ( fd < 0 )
return - EINVAL ;
2013-09-26 15:36:38 +04:00
/* Check the .eh_frame section for unwinding info */
2012-08-07 17:20:46 +04:00
offset = elf_section_offset ( fd , " .eh_frame_hdr " ) ;
close ( fd ) ;
if ( offset )
ret = unwind_spec_ehframe ( dso , machine , offset ,
table_data , segbase ,
fde_count ) ;
return ret ;
}
2013-09-26 15:36:38 +04:00
# ifndef NO_LIBUNWIND_DEBUG_FRAME
static int read_unwind_spec_debug_frame ( struct dso * dso ,
struct machine * machine , u64 * offset )
{
int fd = dso__data_fd ( dso , machine ) ;
if ( fd < 0 )
return - EINVAL ;
/* Check the .debug_frame section for unwinding info */
* offset = elf_section_offset ( fd , " .debug_frame " ) ;
close ( fd ) ;
if ( * offset )
return 0 ;
return - EINVAL ;
}
# endif
2012-08-07 17:20:46 +04:00
static struct map * find_map ( unw_word_t ip , struct unwind_info * ui )
{
struct addr_location al ;
thread__find_addr_map ( ui - > thread , ui - > machine , PERF_RECORD_MISC_USER ,
2013-08-08 15:32:27 +04:00
MAP__FUNCTION , ip , & al ) ;
2012-08-07 17:20:46 +04:00
return al . map ;
}
static int
find_proc_info ( unw_addr_space_t as , unw_word_t ip , unw_proc_info_t * pi ,
int need_unwind_info , void * arg )
{
struct unwind_info * ui = arg ;
struct map * map ;
unw_dyn_info_t di ;
u64 table_data , segbase , fde_count ;
map = find_map ( ip , ui ) ;
if ( ! map | | ! map - > dso )
return - EINVAL ;
pr_debug ( " unwind: find_proc_info dso %s \n " , map - > dso - > name ) ;
2013-09-26 15:36:38 +04:00
/* Check the .eh_frame section for unwinding info */
if ( ! read_unwind_spec_eh_frame ( map - > dso , ui - > machine ,
& table_data , & segbase , & fde_count ) ) {
memset ( & di , 0 , sizeof ( di ) ) ;
di . format = UNW_INFO_FORMAT_REMOTE_TABLE ;
di . start_ip = map - > start ;
di . end_ip = map - > end ;
di . u . rti . segbase = map - > start + segbase ;
di . u . rti . table_data = map - > start + table_data ;
di . u . rti . table_len = fde_count * sizeof ( struct table_entry )
/ sizeof ( unw_word_t ) ;
return dwarf_search_unwind_table ( as , ip , & di , pi ,
need_unwind_info , arg ) ;
}
# ifndef NO_LIBUNWIND_DEBUG_FRAME
/* Check the .debug_frame section for unwinding info */
if ( ! read_unwind_spec_debug_frame ( map - > dso , ui - > machine , & segbase ) ) {
memset ( & di , 0 , sizeof ( di ) ) ;
2013-12-16 20:43:14 +04:00
if ( dwarf_find_debug_frame ( 0 , & di , ip , 0 , map - > dso - > name ,
map - > start , map - > end ) )
return dwarf_search_unwind_table ( as , ip , & di , pi ,
need_unwind_info , arg ) ;
2013-09-26 15:36:38 +04:00
}
# endif
2012-08-07 17:20:46 +04:00
2013-09-26 15:36:38 +04:00
return - EINVAL ;
2012-08-07 17:20:46 +04:00
}
2012-09-11 02:15:03 +04:00
static int access_fpreg ( unw_addr_space_t __maybe_unused as ,
unw_regnum_t __maybe_unused num ,
unw_fpreg_t __maybe_unused * val ,
int __maybe_unused __write ,
void __maybe_unused * arg )
2012-08-07 17:20:46 +04:00
{
pr_err ( " unwind: access_fpreg unsupported \n " ) ;
return - UNW_EINVAL ;
}
2012-09-11 02:15:03 +04:00
static int get_dyn_info_list_addr ( unw_addr_space_t __maybe_unused as ,
unw_word_t __maybe_unused * dil_addr ,
void __maybe_unused * arg )
2012-08-07 17:20:46 +04:00
{
return - UNW_ENOINFO ;
}
2012-09-11 02:15:03 +04:00
static int resume ( unw_addr_space_t __maybe_unused as ,
unw_cursor_t __maybe_unused * cu ,
void __maybe_unused * arg )
2012-08-07 17:20:46 +04:00
{
pr_err ( " unwind: resume unsupported \n " ) ;
return - UNW_EINVAL ;
}
static int
2012-09-11 02:15:03 +04:00
get_proc_name ( unw_addr_space_t __maybe_unused as ,
unw_word_t __maybe_unused addr ,
char __maybe_unused * bufp , size_t __maybe_unused buf_len ,
unw_word_t __maybe_unused * offp , void __maybe_unused * arg )
2012-08-07 17:20:46 +04:00
{
pr_err ( " unwind: get_proc_name unsupported \n " ) ;
return - UNW_EINVAL ;
}
static int access_dso_mem ( struct unwind_info * ui , unw_word_t addr ,
unw_word_t * data )
{
struct addr_location al ;
ssize_t size ;
thread__find_addr_map ( ui - > thread , ui - > machine , PERF_RECORD_MISC_USER ,
2013-08-08 15:32:27 +04:00
MAP__FUNCTION , addr , & al ) ;
2012-08-07 17:20:46 +04:00
if ( ! al . map ) {
pr_debug ( " unwind: no map for %lx \n " , ( unsigned long ) addr ) ;
return - 1 ;
}
if ( ! al . map - > dso )
return - 1 ;
size = dso__data_read_addr ( al . map - > dso , al . map , ui - > machine ,
addr , ( u8 * ) data , sizeof ( * data ) ) ;
return ! ( size = = sizeof ( * data ) ) ;
}
static int reg_value ( unw_word_t * valp , struct regs_dump * regs , int id ,
u64 sample_regs )
{
int i , idx = 0 ;
if ( ! ( sample_regs & ( 1 < < id ) ) )
return - EINVAL ;
for ( i = 0 ; i < id ; i + + ) {
if ( sample_regs & ( 1 < < i ) )
idx + + ;
}
* valp = regs - > regs [ idx ] ;
return 0 ;
}
2012-09-11 02:15:03 +04:00
static int access_mem ( unw_addr_space_t __maybe_unused as ,
2012-08-07 17:20:46 +04:00
unw_word_t addr , unw_word_t * valp ,
int __write , void * arg )
{
struct unwind_info * ui = arg ;
struct stack_dump * stack = & ui - > sample - > user_stack ;
unw_word_t start , end ;
int offset ;
int ret ;
/* Don't support write, probably not needed. */
if ( __write | | ! stack | | ! ui - > sample - > user_regs . regs ) {
* valp = 0 ;
return 0 ;
}
ret = reg_value ( & start , & ui - > sample - > user_regs , PERF_REG_SP ,
ui - > sample_uregs ) ;
if ( ret )
return ret ;
end = start + stack - > size ;
/* Check overflow. */
if ( addr + sizeof ( unw_word_t ) < addr )
return - EINVAL ;
if ( addr < start | | addr + sizeof ( unw_word_t ) > = end ) {
ret = access_dso_mem ( ui , addr , valp ) ;
if ( ret ) {
pr_debug ( " unwind: access_mem %p not inside range %p-%p \n " ,
( void * ) addr , ( void * ) start , ( void * ) end ) ;
* valp = 0 ;
return ret ;
}
return 0 ;
}
offset = addr - start ;
* valp = * ( unw_word_t * ) & stack - > data [ offset ] ;
pr_debug ( " unwind: access_mem addr %p, val %lx, offset %d \n " ,
( void * ) addr , ( unsigned long ) * valp , offset ) ;
return 0 ;
}
2012-09-11 02:15:03 +04:00
static int access_reg ( unw_addr_space_t __maybe_unused as ,
2012-08-07 17:20:46 +04:00
unw_regnum_t regnum , unw_word_t * valp ,
int __write , void * arg )
{
struct unwind_info * ui = arg ;
int id , ret ;
/* Don't support write, I suspect we don't need it. */
if ( __write ) {
pr_err ( " unwind: access_reg w %d \n " , regnum ) ;
return 0 ;
}
if ( ! ui - > sample - > user_regs . regs ) {
* valp = 0 ;
return 0 ;
}
id = unwind__arch_reg_id ( regnum ) ;
if ( id < 0 )
return - EINVAL ;
ret = reg_value ( valp , & ui - > sample - > user_regs , id , ui - > sample_uregs ) ;
if ( ret ) {
pr_err ( " unwind: can't read reg %d \n " , regnum ) ;
return ret ;
}
pr_debug ( " unwind: reg %d, val %lx \n " , regnum , ( unsigned long ) * valp ) ;
return 0 ;
}
2012-09-11 02:15:03 +04:00
static void put_unwind_info ( unw_addr_space_t __maybe_unused as ,
unw_proc_info_t * pi __maybe_unused ,
void * arg __maybe_unused )
2012-08-07 17:20:46 +04:00
{
pr_debug ( " unwind: put_unwind_info called \n " ) ;
}
static int entry ( u64 ip , struct thread * thread , struct machine * machine ,
unwind_entry_cb_t cb , void * arg )
{
struct unwind_entry e ;
struct addr_location al ;
thread__find_addr_location ( thread , machine ,
PERF_RECORD_MISC_USER ,
2013-08-08 15:32:26 +04:00
MAP__FUNCTION , ip , & al ) ;
2012-08-07 17:20:46 +04:00
e . ip = ip ;
e . map = al . map ;
e . sym = al . sym ;
pr_debug ( " unwind: %s:ip = 0x% " PRIx64 " (0x% " PRIx64 " ) \n " ,
al . sym ? al . sym - > name : " '' " ,
ip ,
al . map ? al . map - > map_ip ( al . map , ip ) : ( u64 ) 0 ) ;
return cb ( & e , arg ) ;
}
static void display_error ( int err )
{
switch ( err ) {
case UNW_EINVAL :
pr_err ( " unwind: Only supports local. \n " ) ;
break ;
case UNW_EUNSPEC :
pr_err ( " unwind: Unspecified error. \n " ) ;
break ;
case UNW_EBADREG :
pr_err ( " unwind: Register unavailable. \n " ) ;
break ;
default :
break ;
}
}
static unw_accessors_t accessors = {
. find_proc_info = find_proc_info ,
. put_unwind_info = put_unwind_info ,
. get_dyn_info_list_addr = get_dyn_info_list_addr ,
. access_mem = access_mem ,
. access_reg = access_reg ,
. access_fpreg = access_fpreg ,
. resume = resume ,
. get_proc_name = get_proc_name ,
} ;
static int get_entries ( struct unwind_info * ui , unwind_entry_cb_t cb ,
2013-11-14 00:40:36 +04:00
void * arg , int max_stack )
2012-08-07 17:20:46 +04:00
{
unw_addr_space_t addr_space ;
unw_cursor_t c ;
int ret ;
addr_space = unw_create_addr_space ( & accessors , 0 ) ;
if ( ! addr_space ) {
pr_err ( " unwind: Can't create unwind address space. \n " ) ;
return - ENOMEM ;
}
ret = unw_init_remote ( & c , addr_space , ui ) ;
if ( ret )
display_error ( ret ) ;
2013-11-14 00:40:36 +04:00
while ( ! ret & & ( unw_step ( & c ) > 0 ) & & max_stack - - ) {
2012-08-07 17:20:46 +04:00
unw_word_t ip ;
unw_get_reg ( & c , UNW_REG_IP , & ip ) ;
2014-01-07 16:47:24 +04:00
ret = ip ? entry ( ip , ui - > thread , ui - > machine , cb , arg ) : 0 ;
2012-08-07 17:20:46 +04:00
}
unw_destroy_addr_space ( addr_space ) ;
return ret ;
}
int unwind__get_entries ( unwind_entry_cb_t cb , void * arg ,
struct machine * machine , struct thread * thread ,
2013-11-14 00:40:36 +04:00
u64 sample_uregs , struct perf_sample * data ,
int max_stack )
2012-08-07 17:20:46 +04:00
{
unw_word_t ip ;
struct unwind_info ui = {
. sample = data ,
. sample_uregs = sample_uregs ,
. thread = thread ,
. machine = machine ,
} ;
int ret ;
if ( ! data - > user_regs . regs )
return - EINVAL ;
ret = reg_value ( & ip , & data - > user_regs , PERF_REG_IP , sample_uregs ) ;
if ( ret )
return ret ;
ret = entry ( ip , thread , machine , cb , arg ) ;
if ( ret )
return - ENOMEM ;
2014-01-07 16:47:23 +04:00
return - - max_stack > 0 ? get_entries ( & ui , cb , arg , max_stack ) : 0 ;
2012-08-07 17:20:46 +04:00
}