Use dl_iterate_phdr() in wxDynamicLibrary::ListLoaded()

This has the advantage of returning libraries in their load order, which
is more useful than the unspecified order that was used before.

It also means that this function now has a chance of working under other
systems such as FreeBSD, which also provides dl_iterate_phdr().
This commit is contained in:
Vadim Zeitlin 2023-09-06 00:38:15 +02:00
parent d22275ce0a
commit aec49d9c9d
6 changed files with 75 additions and 76 deletions

View file

@ -565,6 +565,7 @@ check_symbol_exists(dlopen dlfcn.h HAVE_DLOPEN)
cmake_pop_check_state() cmake_pop_check_state()
if(HAVE_DLOPEN) if(HAVE_DLOPEN)
check_symbol_exists(dladdr dlfcn.h HAVE_DLADDR) check_symbol_exists(dladdr dlfcn.h HAVE_DLADDR)
check_symbol_exists(dl_iterate_phdr link.h HAVE_DL_ITERATE_PHDR)
endif() endif()
if(APPLE) if(APPLE)

12
configure vendored
View file

@ -35456,6 +35456,18 @@ fi
fi
done
for ac_func in dl_iterate_phdr
do :
ac_fn_c_check_func "$LINENO" "dl_iterate_phdr" "ac_cv_func_dl_iterate_phdr"
if test "x$ac_cv_func_dl_iterate_phdr" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_DL_ITERATE_PHDR 1
_ACEOF
fi fi
done done

View file

@ -4839,7 +4839,7 @@ else
]) ])
]) ])
dnl check also for dlerror() dnl check also for some optional functions which we may use
if test "$HAVE_DL_FUNCS" = 1; then if test "$HAVE_DL_FUNCS" = 1; then
AC_CHECK_FUNCS(dladdr, AC_CHECK_FUNCS(dladdr,
AC_DEFINE(HAVE_DLADDR), AC_DEFINE(HAVE_DLADDR),
@ -4851,6 +4851,8 @@ else
]) ])
] ]
) )
AC_CHECK_FUNCS(dl_iterate_phdr)
fi fi
fi fi

View file

@ -23,6 +23,10 @@ public:
/** /**
Retrieves the load address and the size of this module. Retrieves the load address and the size of this module.
Note that under ELF systems (such as Linux) the region defined by the
parameters of this function can be discontinuous and contain multiple
segments belonging to the module with holes between them.
@param addr @param addr
The pointer to the location to return load address in, may be The pointer to the location to return load address in, may be
@NULL. @NULL.
@ -38,6 +42,8 @@ public:
/** /**
Returns the base name of this module, e.g.\ @c "kernel32.dll" or Returns the base name of this module, e.g.\ @c "kernel32.dll" or
@c "libc-2.3.2.so". @c "libc-2.3.2.so".
This name is empty for the main program itself.
*/ */
wxString GetName() const; wxString GetName() const;
@ -217,13 +223,20 @@ public:
bool IsLoaded() const; bool IsLoaded() const;
/** /**
This static method returns a wxArray containing the details of all This static method returns a vector-like object containing the details
modules loaded into the address space of the current project. The array of all modules loaded into the address space of the current project.
elements are objects of the type: wxDynamicLibraryDetails. The array
will be empty if an error occurred.
This method is currently implemented only under Win32 and Linux and is The array elements are objects of the type wxDynamicLibraryDetails.
useful mostly for diagnostics purposes. Under Unix systems they appear in the order in which they libraries
have been loaded, with the module corresponding to the main program
itself coming first.
The returned array will be empty if an error occurred or if the
function is not implemented for the current platform.
This method is currently implemented only under Win32 and Unix systems
providing `dl_iterate_phdr()` function (such as Linux) and is useful
mostly for diagnostics purposes.
*/ */
static wxDynamicLibraryDetailsArray ListLoaded(); static wxDynamicLibraryDetailsArray ListLoaded();

View file

@ -935,6 +935,9 @@
/* Define if you have the dladdr function. */ /* Define if you have the dladdr function. */
#undef HAVE_DLADDR #undef HAVE_DLADDR
/* Define if you have the dl_iterate_phdr function. */
#undef HAVE_DL_ITERATE_PHDR
/* Define if you have Posix fnctl() function. */ /* Define if you have Posix fnctl() function. */
#undef HAVE_FCNTL #undef HAVE_FCNTL

View file

@ -33,6 +33,10 @@
#include <dlfcn.h> #include <dlfcn.h>
#endif #endif
#ifdef HAVE_DL_ITERATE_PHDR
#include <link.h>
#endif
// if some flags are not supported, just ignore them // if some flags are not supported, just ignore them
#ifndef RTLD_LAZY #ifndef RTLD_LAZY
#define RTLD_LAZY 0 #define RTLD_LAZY 0
@ -125,20 +129,38 @@ void wxDynamicLibrary::ReportError(const wxString& message,
// listing loaded modules // listing loaded modules
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#ifdef HAVE_DL_ITERATE_PHDR
// wxDynamicLibraryDetails declares this class as its friend, so put the code // wxDynamicLibraryDetails declares this class as its friend, so put the code
// initializing new details objects here // initializing new details objects here
class wxDynamicLibraryDetailsCreator class wxDynamicLibraryDetailsCreator
{ {
public: public:
// create a new wxDynamicLibraryDetails from the given data static int Callback(dl_phdr_info* info, size_t /* size */, void* data)
static wxDynamicLibraryDetails
New(void *start, void *end, const wxString& path)
{ {
const wxString path = wxString::FromUTF8(info->dlpi_name);
wxDynamicLibraryDetails details; wxDynamicLibraryDetails details;
details.m_path = path; details.m_path = path;
details.m_name = path.AfterLast(wxT('/')); details.m_name = path.AfterLast(wxT('/'));
details.m_address = start;
details.m_length = (char *)end - (char *)start; // Find the first and last address belonging to this module.
decltype(info->dlpi_addr) start = 0, end = 0;
for ( decltype(info->dlpi_phnum) n = 0; n < info->dlpi_phnum; n++ )
{
const auto& segment = info->dlpi_phdr[n];
if ( !segment.p_vaddr || !segment.p_memsz )
continue;
if ( !start || segment.p_vaddr < start )
start = segment.p_vaddr;
if ( !end || segment.p_vaddr + segment.p_memsz > end )
end = segment.p_vaddr + segment.p_memsz;
}
details.m_address = wxUIntToPtr(info->dlpi_addr + start);
details.m_length = end - start;
// try to extract the library version from its name // try to extract the library version from its name
const size_t posExt = path.rfind(wxT(".so")); const size_t posExt = path.rfind(wxT(".so"));
@ -161,78 +183,24 @@ public:
} }
} }
return details; auto dlls = static_cast<wxDynamicLibraryDetailsArray*>(data);
dlls->push_back(std::move(details));
// Return 0 to keep iterating.
return 0;
} }
}; };
#endif // HAVE_DL_ITERATE_PHDR
/* static */ /* static */
wxDynamicLibraryDetailsArray wxDynamicLibrary::ListLoaded() wxDynamicLibraryDetailsArray wxDynamicLibrary::ListLoaded()
{ {
wxDynamicLibraryDetailsArray dlls; wxDynamicLibraryDetailsArray dlls;
#ifdef __LINUX__ #ifdef HAVE_DL_ITERATE_PHDR
// examine /proc/self/maps to find out what is loaded in our address space dl_iterate_phdr(wxDynamicLibraryDetailsCreator::Callback, &dlls);
wxFFile file(wxT("/proc/self/maps")); #endif // HAVE_DL_ITERATE_PHDR
if ( file.IsOpened() )
{
// details of the module currently being parsed
wxString pathCur;
void *startCur = nullptr,
*endCur = nullptr;
char path[1024];
char buf[1024];
while ( fgets(buf, WXSIZEOF(buf), file.fp()) )
{
// format is: "start-end perm offset maj:min inode path", see proc(5)
void *start,
*end;
switch ( sscanf(buf, "%p-%p %*4s %*p %*02x:%*02x %*d %1023s\n",
&start, &end, path) )
{
case 2:
// there may be no path column
path[0] = '\0';
break;
case 3:
// nothing to do, read everything we wanted
break;
default:
// chop '\n'
buf[strlen(buf) - 1] = '\0';
wxLogDebug(wxT("Failed to parse line \"%s\" in /proc/self/maps."),
buf);
continue;
}
wxASSERT_MSG( start >= endCur,
wxT("overlapping regions in /proc/self/maps?") );
wxString pathNew = wxString::FromAscii(path);
if ( pathCur.empty() )
{
// new module start
pathCur = pathNew;
startCur = start;
endCur = end;
}
else if ( pathCur == pathNew && endCur == end )
{
// continuation of the same module in the address space
endCur = end;
}
else // end of the current module
{
dlls.Add(wxDynamicLibraryDetailsCreator::New(startCur,
endCur,
pathCur));
pathCur.clear();
}
}
}
#endif // __LINUX__
return dlls; return dlls;
} }