2023-08-09 10:34:20 +02:00
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2023-09-18 02:48:12 +00:00
# ifndef _GNU_SOURCE
# define _GNU_SOURCE
# endif
2023-08-09 10:34:20 +02:00
# include <libelf.h>
# include <gelf.h>
# include <fcntl.h>
# include <linux/kernel.h>
# include "libbpf_internal.h"
# include "str_error.h"
2023-09-18 02:48:12 +00:00
/* A SHT_GNU_versym section holds 16-bit words. This bit is set if
* the symbol is hidden and can only be seen when referenced using an
* explicit version number . This is a GNU extension .
*/
# define VERSYM_HIDDEN 0x8000
/* This is the mask for the rest of the data in a word read from a
* SHT_GNU_versym section .
*/
# define VERSYM_VERSION 0x7fff
2023-08-09 10:34:21 +02:00
int elf_open ( const char * binary_path , struct elf_fd * elf_fd )
{
char errmsg [ STRERR_BUFSIZE ] ;
int fd , ret ;
Elf * elf ;
if ( elf_version ( EV_CURRENT ) = = EV_NONE ) {
pr_warn ( " elf: failed to init libelf for %s \n " , binary_path ) ;
return - LIBBPF_ERRNO__LIBELF ;
}
fd = open ( binary_path , O_RDONLY | O_CLOEXEC ) ;
if ( fd < 0 ) {
ret = - errno ;
pr_warn ( " elf: failed to open %s: %s \n " , binary_path ,
libbpf_strerror_r ( ret , errmsg , sizeof ( errmsg ) ) ) ;
return ret ;
}
elf = elf_begin ( fd , ELF_C_READ_MMAP , NULL ) ;
if ( ! elf ) {
pr_warn ( " elf: could not read elf from %s: %s \n " , binary_path , elf_errmsg ( - 1 ) ) ;
close ( fd ) ;
return - LIBBPF_ERRNO__FORMAT ;
}
elf_fd - > fd = fd ;
elf_fd - > elf = elf ;
return 0 ;
}
void elf_close ( struct elf_fd * elf_fd )
{
if ( ! elf_fd )
return ;
elf_end ( elf_fd - > elf ) ;
close ( elf_fd - > fd ) ;
}
2023-08-09 10:34:20 +02:00
/* Return next ELF section of sh_type after scn, or first of that type if scn is NULL. */
static Elf_Scn * elf_find_next_scn_by_type ( Elf * elf , int sh_type , Elf_Scn * scn )
{
while ( ( scn = elf_nextscn ( elf , scn ) ) ! = NULL ) {
GElf_Shdr sh ;
if ( ! gelf_getshdr ( scn , & sh ) )
continue ;
if ( sh . sh_type = = sh_type )
return scn ;
}
return NULL ;
}
2023-08-09 10:34:22 +02:00
struct elf_sym {
const char * name ;
GElf_Sym sym ;
GElf_Shdr sh ;
2023-09-18 02:48:12 +00:00
int ver ;
bool hidden ;
2023-08-09 10:34:22 +02:00
} ;
struct elf_sym_iter {
Elf * elf ;
Elf_Data * syms ;
2023-09-18 02:48:12 +00:00
Elf_Data * versyms ;
Elf_Data * verdefs ;
2023-08-09 10:34:22 +02:00
size_t nr_syms ;
size_t strtabidx ;
2023-09-18 02:48:12 +00:00
size_t verdef_strtabidx ;
2023-08-09 10:34:22 +02:00
size_t next_sym_idx ;
struct elf_sym sym ;
int st_type ;
} ;
static int elf_sym_iter_new ( struct elf_sym_iter * iter ,
Elf * elf , const char * binary_path ,
int sh_type , int st_type )
{
Elf_Scn * scn = NULL ;
GElf_Ehdr ehdr ;
GElf_Shdr sh ;
memset ( iter , 0 , sizeof ( * iter ) ) ;
if ( ! gelf_getehdr ( elf , & ehdr ) ) {
pr_warn ( " elf: failed to get ehdr from %s: %s \n " , binary_path , elf_errmsg ( - 1 ) ) ;
return - EINVAL ;
}
scn = elf_find_next_scn_by_type ( elf , sh_type , NULL ) ;
if ( ! scn ) {
pr_debug ( " elf: failed to find symbol table ELF sections in '%s' \n " ,
binary_path ) ;
return - ENOENT ;
}
if ( ! gelf_getshdr ( scn , & sh ) )
return - EINVAL ;
iter - > strtabidx = sh . sh_link ;
iter - > syms = elf_getdata ( scn , 0 ) ;
if ( ! iter - > syms ) {
pr_warn ( " elf: failed to get symbols for symtab section in '%s': %s \n " ,
binary_path , elf_errmsg ( - 1 ) ) ;
return - EINVAL ;
}
iter - > nr_syms = iter - > syms - > d_size / sh . sh_entsize ;
iter - > elf = elf ;
iter - > st_type = st_type ;
2023-09-18 02:48:12 +00:00
/* Version symbol table is meaningful to dynsym only */
if ( sh_type ! = SHT_DYNSYM )
return 0 ;
scn = elf_find_next_scn_by_type ( elf , SHT_GNU_versym , NULL ) ;
if ( ! scn )
return 0 ;
iter - > versyms = elf_getdata ( scn , 0 ) ;
scn = elf_find_next_scn_by_type ( elf , SHT_GNU_verdef , NULL ) ;
2023-10-16 11:28:40 -07:00
if ( ! scn )
return 0 ;
iter - > verdefs = elf_getdata ( scn , 0 ) ;
if ( ! iter - > verdefs | | ! gelf_getshdr ( scn , & sh ) ) {
pr_warn ( " elf: failed to get verdef ELF section in '%s' \n " , binary_path ) ;
2023-09-18 02:48:12 +00:00
return - EINVAL ;
2023-10-16 11:28:40 -07:00
}
2023-09-18 02:48:12 +00:00
iter - > verdef_strtabidx = sh . sh_link ;
2023-08-09 10:34:22 +02:00
return 0 ;
}
static struct elf_sym * elf_sym_iter_next ( struct elf_sym_iter * iter )
{
struct elf_sym * ret = & iter - > sym ;
GElf_Sym * sym = & ret - > sym ;
const char * name = NULL ;
2023-09-18 02:48:12 +00:00
GElf_Versym versym ;
2023-08-09 10:34:22 +02:00
Elf_Scn * sym_scn ;
size_t idx ;
for ( idx = iter - > next_sym_idx ; idx < iter - > nr_syms ; idx + + ) {
if ( ! gelf_getsym ( iter - > syms , idx , sym ) )
continue ;
if ( GELF_ST_TYPE ( sym - > st_info ) ! = iter - > st_type )
continue ;
name = elf_strptr ( iter - > elf , iter - > strtabidx , sym - > st_name ) ;
if ( ! name )
continue ;
sym_scn = elf_getscn ( iter - > elf , sym - > st_shndx ) ;
if ( ! sym_scn )
continue ;
if ( ! gelf_getshdr ( sym_scn , & ret - > sh ) )
continue ;
iter - > next_sym_idx = idx + 1 ;
ret - > name = name ;
2023-09-18 02:48:12 +00:00
ret - > ver = 0 ;
ret - > hidden = false ;
if ( iter - > versyms ) {
if ( ! gelf_getversym ( iter - > versyms , idx , & versym ) )
continue ;
ret - > ver = versym & VERSYM_VERSION ;
ret - > hidden = versym & VERSYM_HIDDEN ;
}
2023-08-09 10:34:22 +02:00
return ret ;
}
return NULL ;
}
2023-09-18 02:48:12 +00:00
static const char * elf_get_vername ( struct elf_sym_iter * iter , int ver )
{
GElf_Verdaux verdaux ;
GElf_Verdef verdef ;
int offset ;
2023-10-16 11:28:40 -07:00
if ( ! iter - > verdefs )
return NULL ;
2023-09-18 02:48:12 +00:00
offset = 0 ;
while ( gelf_getverdef ( iter - > verdefs , offset , & verdef ) ) {
if ( verdef . vd_ndx ! = ver ) {
if ( ! verdef . vd_next )
break ;
offset + = verdef . vd_next ;
continue ;
}
if ( ! gelf_getverdaux ( iter - > verdefs , offset + verdef . vd_aux , & verdaux ) )
break ;
return elf_strptr ( iter - > elf , iter - > verdef_strtabidx , verdaux . vda_name ) ;
}
return NULL ;
}
static bool symbol_match ( struct elf_sym_iter * iter , int sh_type , struct elf_sym * sym ,
const char * name , size_t name_len , const char * lib_ver )
{
const char * ver_name ;
/* Symbols are in forms of func, func@LIB_VER or func@@LIB_VER
* make sure the func part matches the user specified name
*/
if ( strncmp ( sym - > name , name , name_len ) ! = 0 )
return false ;
/* ...but we don't want a search for "foo" to match 'foo2" also, so any
* additional characters in sname should be of the form " @@LIB " .
*/
if ( sym - > name [ name_len ] ! = ' \0 ' & & sym - > name [ name_len ] ! = ' @ ' )
return false ;
/* If user does not specify symbol version, then we got a match */
if ( ! lib_ver )
return true ;
/* If user specifies symbol version, for dynamic symbols,
* get version name from ELF verdef section for comparison .
*/
if ( sh_type = = SHT_DYNSYM ) {
ver_name = elf_get_vername ( iter , sym - > ver ) ;
if ( ! ver_name )
return false ;
return strcmp ( ver_name , lib_ver ) = = 0 ;
}
/* For normal symbols, it is already in form of func@LIB_VER */
return strcmp ( sym - > name , name ) = = 0 ;
}
2023-08-09 10:34:22 +02:00
/* Transform symbol's virtual address (absolute for binaries and relative
* for shared libs ) into file offset , which is what kernel is expecting
* for uprobe / uretprobe attachment .
* See Documentation / trace / uprobetracer . rst for more details . This is done
* by looking up symbol ' s containing section ' s header and using iter ' s virtual
* address ( sh_addr ) and corresponding file offset ( sh_offset ) to transform
* sym . st_value ( virtual address ) into desired final file offset .
*/
static unsigned long elf_sym_offset ( struct elf_sym * sym )
{
return sym - > sym . st_value - sym - > sh . sh_addr + sym - > sh . sh_offset ;
}
2023-08-09 10:34:20 +02:00
/* Find offset of function name in the provided ELF object. "binary_path" is
* the path to the ELF binary represented by " elf " , and only used for error
* reporting matters . " name " matches symbol name or name @ @ LIB for library
* functions .
*/
long elf_find_func_offset ( Elf * elf , const char * binary_path , const char * name )
{
int i , sh_types [ 2 ] = { SHT_DYNSYM , SHT_SYMTAB } ;
2023-09-18 02:48:12 +00:00
const char * at_symbol , * lib_ver ;
bool is_shared_lib ;
2023-08-09 10:34:20 +02:00
long ret = - ENOENT ;
size_t name_len ;
GElf_Ehdr ehdr ;
if ( ! gelf_getehdr ( elf , & ehdr ) ) {
pr_warn ( " elf: failed to get ehdr from %s: %s \n " , binary_path , elf_errmsg ( - 1 ) ) ;
ret = - LIBBPF_ERRNO__FORMAT ;
goto out ;
}
/* for shared lib case, we do not need to calculate relative offset */
is_shared_lib = ehdr . e_type = = ET_DYN ;
2023-09-18 02:48:12 +00:00
/* Does name specify "@@LIB_VER" or "@LIB_VER" ? */
at_symbol = strchr ( name , ' @ ' ) ;
if ( at_symbol ) {
name_len = at_symbol - name ;
/* skip second @ if it's @@LIB_VER case */
if ( at_symbol [ 1 ] = = ' @ ' )
at_symbol + + ;
lib_ver = at_symbol + 1 ;
} else {
name_len = strlen ( name ) ;
lib_ver = NULL ;
}
2023-08-09 10:34:20 +02:00
/* Search SHT_DYNSYM, SHT_SYMTAB for symbol. This search order is used because if
* a binary is stripped , it may only have SHT_DYNSYM , and a fully - statically
* linked binary may not have SHT_DYMSYM , so absence of a section should not be
* reported as a warning / error .
*/
for ( i = 0 ; i < ARRAY_SIZE ( sh_types ) ; i + + ) {
2023-08-09 10:34:22 +02:00
struct elf_sym_iter iter ;
struct elf_sym * sym ;
2023-08-09 10:34:20 +02:00
int last_bind = - 1 ;
2023-08-09 10:34:22 +02:00
int cur_bind ;
2023-08-09 10:34:20 +02:00
2023-08-09 10:34:22 +02:00
ret = elf_sym_iter_new ( & iter , elf , binary_path , sh_types [ i ] , STT_FUNC ) ;
if ( ret = = - ENOENT )
2023-08-09 10:34:20 +02:00
continue ;
2023-08-09 10:34:22 +02:00
if ( ret )
2023-08-09 10:34:20 +02:00
goto out ;
2023-08-09 10:34:22 +02:00
while ( ( sym = elf_sym_iter_next ( & iter ) ) ) {
2023-09-18 02:48:12 +00:00
if ( ! symbol_match ( & iter , sh_types [ i ] , sym , name , name_len , lib_ver ) )
2023-08-09 10:34:20 +02:00
continue ;
2023-08-09 10:34:22 +02:00
cur_bind = GELF_ST_BIND ( sym - > sym . st_info ) ;
if ( ret > 0 ) {
2023-08-09 10:34:20 +02:00
/* handle multiple matches */
2023-09-18 02:48:11 +00:00
if ( elf_sym_offset ( sym ) = = ret ) {
/* same offset, no problem */
continue ;
} else if ( last_bind ! = STB_WEAK & & cur_bind ! = STB_WEAK ) {
2023-08-09 10:34:20 +02:00
/* Only accept one non-weak bind. */
pr_warn ( " elf: ambiguous match for '%s', '%s' in '%s' \n " ,
2023-08-09 10:34:22 +02:00
sym - > name , name , binary_path ) ;
2023-08-09 10:34:20 +02:00
ret = - LIBBPF_ERRNO__FORMAT ;
goto out ;
2023-08-09 10:34:22 +02:00
} else if ( cur_bind = = STB_WEAK ) {
2023-08-09 10:34:20 +02:00
/* already have a non-weak bind, and
* this is a weak bind , so ignore .
*/
continue ;
}
}
2023-08-09 10:34:22 +02:00
ret = elf_sym_offset ( sym ) ;
last_bind = cur_bind ;
2023-08-09 10:34:20 +02:00
}
if ( ret > 0 )
break ;
}
if ( ret > 0 ) {
pr_debug ( " elf: symbol address match for '%s' in '%s': 0x%lx \n " , name , binary_path ,
ret ) ;
} else {
if ( ret = = 0 ) {
pr_warn ( " elf: '%s' is 0 in symtab for '%s': %s \n " , name , binary_path ,
is_shared_lib ? " should not be 0 in a shared library " :
" try using shared library path instead " ) ;
ret = - ENOENT ;
} else {
pr_warn ( " elf: failed to find symbol '%s' in '%s' \n " , name , binary_path ) ;
}
}
out :
return ret ;
}
/* Find offset of function name in ELF object specified by path. "name" matches
* symbol name or name @ @ LIB for library functions .
*/
long elf_find_func_offset_from_file ( const char * binary_path , const char * name )
{
2023-08-09 10:34:21 +02:00
struct elf_fd elf_fd ;
2023-08-09 10:34:20 +02:00
long ret = - ENOENT ;
2023-08-09 10:34:21 +02:00
ret = elf_open ( binary_path , & elf_fd ) ;
if ( ret )
2023-08-09 10:34:20 +02:00
return ret ;
2023-08-09 10:34:21 +02:00
ret = elf_find_func_offset ( elf_fd . elf , binary_path , name ) ;
elf_close ( & elf_fd ) ;
2023-08-09 10:34:20 +02:00
return ret ;
}
2023-08-09 10:34:23 +02:00
struct symbol {
const char * name ;
int bind ;
int idx ;
} ;
static int symbol_cmp ( const void * a , const void * b )
{
const struct symbol * sym_a = a ;
const struct symbol * sym_b = b ;
return strcmp ( sym_a - > name , sym_b - > name ) ;
}
/*
* Return offsets in @ poffsets for symbols specified in @ syms array argument .
* On success returns 0 and offsets are returned in allocated array with @ cnt
* size , that needs to be released by the caller .
*/
int elf_resolve_syms_offsets ( const char * binary_path , int cnt ,
2023-11-25 20:31:25 +01:00
const char * * syms , unsigned long * * poffsets ,
int st_type )
2023-08-09 10:34:23 +02:00
{
int sh_types [ 2 ] = { SHT_DYNSYM , SHT_SYMTAB } ;
int err = 0 , i , cnt_done = 0 ;
unsigned long * offsets ;
struct symbol * symbols ;
struct elf_fd elf_fd ;
err = elf_open ( binary_path , & elf_fd ) ;
if ( err )
return err ;
offsets = calloc ( cnt , sizeof ( * offsets ) ) ;
symbols = calloc ( cnt , sizeof ( * symbols ) ) ;
if ( ! offsets | | ! symbols ) {
err = - ENOMEM ;
goto out ;
}
for ( i = 0 ; i < cnt ; i + + ) {
symbols [ i ] . name = syms [ i ] ;
symbols [ i ] . idx = i ;
}
qsort ( symbols , cnt , sizeof ( * symbols ) , symbol_cmp ) ;
for ( i = 0 ; i < ARRAY_SIZE ( sh_types ) ; i + + ) {
struct elf_sym_iter iter ;
struct elf_sym * sym ;
2023-11-25 20:31:25 +01:00
err = elf_sym_iter_new ( & iter , elf_fd . elf , binary_path , sh_types [ i ] , st_type ) ;
2023-08-09 10:34:23 +02:00
if ( err = = - ENOENT )
continue ;
if ( err )
goto out ;
while ( ( sym = elf_sym_iter_next ( & iter ) ) ) {
unsigned long sym_offset = elf_sym_offset ( sym ) ;
int bind = GELF_ST_BIND ( sym - > sym . st_info ) ;
struct symbol * found , tmp = {
. name = sym - > name ,
} ;
unsigned long * offset ;
found = bsearch ( & tmp , symbols , cnt , sizeof ( * symbols ) , symbol_cmp ) ;
if ( ! found )
continue ;
offset = & offsets [ found - > idx ] ;
if ( * offset > 0 ) {
/* same offset, no problem */
if ( * offset = = sym_offset )
continue ;
/* handle multiple matches */
if ( found - > bind ! = STB_WEAK & & bind ! = STB_WEAK ) {
/* Only accept one non-weak bind. */
pr_warn ( " elf: ambiguous match found '%s@%lu' in '%s' previous offset %lu \n " ,
sym - > name , sym_offset , binary_path , * offset ) ;
err = - ESRCH ;
goto out ;
} else if ( bind = = STB_WEAK ) {
/* already have a non-weak bind, and
* this is a weak bind , so ignore .
*/
continue ;
}
} else {
cnt_done + + ;
}
* offset = sym_offset ;
found - > bind = bind ;
}
}
if ( cnt ! = cnt_done ) {
err = - ENOENT ;
goto out ;
}
* poffsets = offsets ;
out :
free ( symbols ) ;
if ( err )
free ( offsets ) ;
elf_close ( & elf_fd ) ;
return err ;
}
2023-08-09 10:34:24 +02:00
/*
* Return offsets in @ poffsets for symbols specified by @ pattern argument .
* On success returns 0 and offsets are returned in allocated @ poffsets
* array with the @ pctn size , that needs to be released by the caller .
*/
int elf_resolve_pattern_offsets ( const char * binary_path , const char * pattern ,
unsigned long * * poffsets , size_t * pcnt )
{
int sh_types [ 2 ] = { SHT_SYMTAB , SHT_DYNSYM } ;
unsigned long * offsets = NULL ;
size_t cap = 0 , cnt = 0 ;
struct elf_fd elf_fd ;
int err = 0 , i ;
err = elf_open ( binary_path , & elf_fd ) ;
if ( err )
return err ;
for ( i = 0 ; i < ARRAY_SIZE ( sh_types ) ; i + + ) {
struct elf_sym_iter iter ;
struct elf_sym * sym ;
err = elf_sym_iter_new ( & iter , elf_fd . elf , binary_path , sh_types [ i ] , STT_FUNC ) ;
if ( err = = - ENOENT )
continue ;
if ( err )
goto out ;
while ( ( sym = elf_sym_iter_next ( & iter ) ) ) {
if ( ! glob_match ( sym - > name , pattern ) )
continue ;
err = libbpf_ensure_mem ( ( void * * ) & offsets , & cap , sizeof ( * offsets ) ,
cnt + 1 ) ;
if ( err )
goto out ;
offsets [ cnt + + ] = elf_sym_offset ( sym ) ;
}
/* If we found anything in the first symbol section,
* do not search others to avoid duplicates .
*/
if ( cnt )
break ;
}
if ( cnt ) {
* poffsets = offsets ;
* pcnt = cnt ;
} else {
err = - ENOENT ;
}
out :
if ( err )
free ( offsets ) ;
elf_close ( & elf_fd ) ;
return err ;
}