diff --git a/client/TracyProfiler.cpp b/client/TracyProfiler.cpp index 9b15a2d5..12b7e59c 100644 --- a/client/TracyProfiler.cpp +++ b/client/TracyProfiler.cpp @@ -32,6 +32,14 @@ # include #endif +#ifdef __ANDROID__ +# include +# include +# include +# include +# include +#endif + #if defined __APPLE__ || defined BSD # include # include @@ -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; +using MappingInfoIterator = MappingInfoVector::iterator; + +// Parses /proc/self/maps returning a vector. +// /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( 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 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 ); }