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()
if(HAVE_DLOPEN)
check_symbol_exists(dladdr dlfcn.h HAVE_DLADDR)
check_symbol_exists(dl_iterate_phdr link.h HAVE_DL_ITERATE_PHDR)
endif()
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
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
AC_CHECK_FUNCS(dladdr,
AC_DEFINE(HAVE_DLADDR),
@ -4851,6 +4851,8 @@ else
])
]
)
AC_CHECK_FUNCS(dl_iterate_phdr)
fi
fi

View file

@ -23,6 +23,10 @@ public:
/**
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
The pointer to the location to return load address in, may be
@NULL.
@ -38,6 +42,8 @@ public:
/**
Returns the base name of this module, e.g.\ @c "kernel32.dll" or
@c "libc-2.3.2.so".
This name is empty for the main program itself.
*/
wxString GetName() const;
@ -217,13 +223,20 @@ public:
bool IsLoaded() const;
/**
This static method returns a wxArray containing the details of all
modules loaded into the address space of the current project. The array
elements are objects of the type: wxDynamicLibraryDetails. The array
will be empty if an error occurred.
This static method returns a vector-like object containing the details
of all modules loaded into the address space of the current project.
This method is currently implemented only under Win32 and Linux and is
useful mostly for diagnostics purposes.
The array elements are objects of the type wxDynamicLibraryDetails.
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();

View file

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

View file

@ -33,6 +33,10 @@
#include <dlfcn.h>
#endif
#ifdef HAVE_DL_ITERATE_PHDR
#include <link.h>
#endif
// if some flags are not supported, just ignore them
#ifndef RTLD_LAZY
#define RTLD_LAZY 0
@ -125,20 +129,38 @@ void wxDynamicLibrary::ReportError(const wxString& message,
// listing loaded modules
// ----------------------------------------------------------------------------
#ifdef HAVE_DL_ITERATE_PHDR
// wxDynamicLibraryDetails declares this class as its friend, so put the code
// initializing new details objects here
class wxDynamicLibraryDetailsCreator
{
public:
// create a new wxDynamicLibraryDetails from the given data
static wxDynamicLibraryDetails
New(void *start, void *end, const wxString& path)
static int Callback(dl_phdr_info* info, size_t /* size */, void* data)
{
const wxString path = wxString::FromUTF8(info->dlpi_name);
wxDynamicLibraryDetails details;
details.m_path = path;
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
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 */
wxDynamicLibraryDetailsArray wxDynamicLibrary::ListLoaded()
{
wxDynamicLibraryDetailsArray dlls;
#ifdef __LINUX__
// examine /proc/self/maps to find out what is loaded in our address space
wxFFile file(wxT("/proc/self/maps"));
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__
#ifdef HAVE_DL_ITERATE_PHDR
dl_iterate_phdr(wxDynamicLibraryDetailsCreator::Callback, &dlls);
#endif // HAVE_DL_ITERATE_PHDR
return dlls;
}