1
0
mirror of https://github.com/wolfpld/tracy.git synced 2025-03-20 07:40:02 +08:00

Ensure that mappings have read permission before reading symbol code.

Fixes #125
This commit is contained in:
Benoit Jacob 2020-11-19 14:08:37 -05:00
parent 37c9a5807f
commit 3721d63f9f

View File

@ -32,6 +32,14 @@
# include <sys/syscall.h>
#endif
#ifdef __ANDROID__
# include <sys/mman.h>
# include <cstdio>
# include <cstdint>
# include <vector>
# include <mutex>
#endif
#if defined __APPLE__ || defined BSD
# include <sys/types.h>
# include <sys/sysctl.h>
@ -2907,8 +2915,189 @@ void Profiler::HandleSymbolQuery( uint64_t symbol )
#endif
}
#ifdef __ANDROID__
// Implementation of EnsureReadable, a helper to ensure that we can read symbols code.
// This is so far only needed on Android, where it is common for libraries to be mapped
// with only executable, not readable, permissions. Typical example (line from /proc/self/maps):
/*
746b63b000-746b6dc000 --xp 00042000 07:48 35 /apex/com.android.runtime/lib64/bionic/libc.so
*/
// See https://github.com/wolfpld/tracy/issues/125 .
// To work around this, we parse /proc/self/maps and we use mprotect to set read permissions
// on any mappings that are touched by address ranges that HandleSymbolCodeQuery needs to read.
namespace {
// Holds some information about a single memory mapping.
struct MappingInfo {
// Start of address range. Inclusive.
std::uintptr_t start_address;
// End of address range. Exclusive, so the mapping is the half-open interval
// [start, end) and its length in bytes is `end - start`). As in /proc/self/maps.
std::uintptr_t end_address;
// Read/Write/Executable permissions.
bool perm_r, perm_w, perm_x;
// Flag indicating if a `mprotect` call has already failed on this mapping,
// so we don't retry over and over again.
bool mprotect_fails = false;
};
using MappingInfoVector = std::vector<MappingInfo>;
using MappingInfoIterator = MappingInfoVector::iterator;
// Parses /proc/self/maps returning a vector<MappingInfo>.
// /proc/self/maps is assumed to be sorted by ascending address, so the resulting
// vector is sorted by ascending address too.
MappingInfoVector ParseMappings()
{
MappingInfoVector result;
FILE* file = fopen( "/proc/self/maps", "r" );
if( !file ) return result;
char line[256];
while( fgets( line, sizeof( line ), file ) )
{
std::uintptr_t start_addr;
std::uintptr_t end_addr;
if( sscanf( line, "%lx-%lx", &start_addr, &end_addr ) != 2 ) continue;
char* first_space = strchr( line, ' ' );
if( !first_space ) continue;
char* perm = first_space + 1;
char* second_space = strchr( perm, ' ' );
if( !second_space || second_space - perm != 4 ) continue;
result.emplace_back();
auto& mapping = result.back();
mapping.start_address = start_addr;
mapping.end_address = end_addr;
mapping.perm_r = perm[0] == 'r';
mapping.perm_w = perm[1] == 'w';
mapping.perm_x = perm[2] == 'x';
}
fclose( file );
return result;
}
bool GetMappingsRange(MappingInfoVector& mappings,
std::uintptr_t start_address,
std::uintptr_t end_address,
MappingInfoIterator* start_iterator,
MappingInfoIterator* end_iterator)
{
// Find the first mapping intersecting the requested address range.
// We assume mappings to be sorted by address, as /proc/self/maps seems to be.
// Construct a MappingInfo just for the purpose of using std::lower_bound.
// (It's an abuse of MappingInfo since this address range is not necessarily
// one mapping).
MappingInfo address_range;
address_range.start_address = start_address;
address_range.end_address = end_address;
// Comparison function for std::lower_bound. Returns true if all addresses in `m1`
// are lower than all addresses in `m2`.
auto Compare = []( const MappingInfo& m1, const MappingInfo& m2 ) {
// '<=' because the address ranges are half-open intervals, [start, end).
return m1.end_address <= m2.start_address;
};
MappingInfoIterator it = std::lower_bound( mappings.begin(), mappings.end(), address_range, Compare );
*start_iterator = it;
std::uintptr_t remainder_start_address = start_address;
// Loop over all contiguous mappings intersecting the requested address range.
while( it != mappings.end() && it->start_address < end_address ) {
if (it->start_address > remainder_start_address) {
// The current mapping starts after the remainder of the address
// range that we needs to cover, so some of that address range
// isn't covered by any mapping.
return false;
}
remainder_start_address = it->end_address;
++it;
}
// Some of the address range isn't covered by any mapping.
if (remainder_start_address < end_address) return false;
*end_iterator = it;
if (*end_iterator < *start_iterator) {
abort();
}
if (*end_iterator > (*start_iterator) + 1) {
abort();
}
return true;
}
// Attempts to make the specified `mapping` readable if it isn't already.
// Returns true if and only if the mapping is readable.
bool EnsureReadable( MappingInfo* mapping )
{
if( mapping->perm_r )
{
// The mapping is already readable.
return true;
}
if( mapping->mprotect_fails )
{
// We have already see mprotect failing on this mapping.
return false;
}
int prot = PROT_READ;
if( mapping->perm_w ) prot |= PROT_WRITE;
if( mapping->perm_x ) prot |= PROT_EXEC;
if( mprotect( reinterpret_cast<void*>( mapping->start_address ),
mapping->end_address - mapping->start_address, prot ) == -1 )
{
// Failed to make the mapping readable. Update `mapping`
// so the next call will be fast.
mapping->mprotect_fails = true;
return false;
}
// The mapping is now readable. Update `mapping` so the next call will be fast.
mapping->perm_r = true;
return true;
}
// Attempts to make the specified address range readable.
bool EnsureReadable( std::uintptr_t start_address, std::uintptr_t end_address )
{
// Lock ensuring reentrancy of this function.
static std::mutex s_mutex;
const std::lock_guard<std::mutex> lock( s_mutex );
// Static state managed by this function. Not constant, we mutate that state as
// we turn some mappings readable. Initially parsed once here, updated as needed below.
static MappingInfoVector mappings = ParseMappings();
MappingInfoIterator start_iter, end_iter;
if (!GetMappingsRange(mappings, start_address, end_address, &start_iter, &end_iter))
{
// Some of this address range isn't in any known mapping. Try parsing agains, maybe
// mappings changed.
mappings = ParseMappings();
if (!GetMappingsRange(mappings, start_address, end_address, &start_iter, &end_iter))
{
// Still not mapped? This shouldn't happen...
return false;
}
}
if (end_iter < start_iter) {
abort();
}
int k = 0;
for (auto it = start_iter; it != end_iter; ++it)
{
if( !EnsureReadable( &*it ) ) return false;
}
return true;
}
} // anonymous namespace
#endif // defined __ANDROID__
void Profiler::HandleSymbolCodeQuery( uint64_t symbol, uint32_t size )
{
#ifdef __ANDROID__
// On Android it's common for code to be in mappings that are only executable
// but not readable.
if (!EnsureReadable( symbol, symbol + size ) )
{
// Non-readable addresses: bail here, avoid segfault below.
return;
}
#endif
SendLongString( symbol, (const char*)symbol, size, QueueType::SymbolCode );
}