Skip to content

Commit dadecd8

Browse files
authored
Downport async-profiler no-allocation changes (#245)
1 parent f5bb495 commit dadecd8

File tree

5 files changed

+196
-73
lines changed

5 files changed

+196
-73
lines changed

ddprof-lib/src/main/cpp/codeCache.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ char *NativeFunc::create(const char *name, short lib_index) {
2222
void NativeFunc::destroy(char *name) { free(from(name)); }
2323

2424
CodeCache::CodeCache(const char *name, short lib_index, bool imports_patchable,
25-
const void *min_address, const void *max_address) {
25+
const void *min_address, const void *max_address,
26+
const char* image_base) {
2627
_name = NativeFunc::create(name, -1);
2728
_lib_index = lib_index;
2829
_min_address = min_address;
2930
_max_address = max_address;
3031
_text_base = NULL;
32+
_image_base = image_base;
3133

3234
_plt_offset = 0;
3335
_plt_size = 0;

ddprof-lib/src/main/cpp/codeCache.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class CodeCache {
109109
const void *_min_address;
110110
const void *_max_address;
111111
const char *_text_base;
112+
const char* _image_base;
112113

113114
unsigned int _plt_offset;
114115
unsigned int _plt_size;
@@ -132,7 +133,8 @@ class CodeCache {
132133
explicit CodeCache(const char *name, short lib_index = -1,
133134
bool imports_patchable = false,
134135
const void *min_address = NO_MIN_ADDRESS,
135-
const void *max_address = NO_MAX_ADDRESS);
136+
const void *max_address = NO_MAX_ADDRESS,
137+
const char* image_base = NULL);
136138
// Copy constructor
137139
CodeCache(const CodeCache &other);
138140
// Copy assignment operator
@@ -148,6 +150,8 @@ class CodeCache {
148150

149151
const void *maxAddress() const { return _max_address; }
150152

153+
const char* imageBase() const { return _image_base; }
154+
151155
bool contains(const void *address) const {
152156
return address >= _min_address && address < _max_address;
153157
}

ddprof-lib/src/main/cpp/symbols.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Symbols {
2424
private:
2525
static Mutex _parse_lock;
2626
static bool _have_kernel_symbols;
27+
static bool _libs_limit_reported;
2728

2829
public:
2930
static void parseKernelSymbols(CodeCache *cc);
@@ -37,4 +38,18 @@ class Symbols {
3738
static bool isRootSymbol(const void* address);
3839
};
3940

41+
class UnloadProtection {
42+
private:
43+
void* _lib_handle;
44+
bool _valid;
45+
46+
public:
47+
UnloadProtection(const CodeCache *cc);
48+
~UnloadProtection();
49+
50+
UnloadProtection& operator=(const UnloadProtection& other) = delete;
51+
52+
bool isValid() const { return _valid; }
53+
};
54+
4055
#endif // _SYMBOLS_H

ddprof-lib/src/main/cpp/symbols_linux.cpp

Lines changed: 142 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@
1212
#include "log.h"
1313
#include "safeAccess.h"
1414
#include "symbols.h"
15+
#include <dlfcn.h>
1516
#include <elf.h>
1617
#include <errno.h>
1718
#include <fcntl.h>
1819
#include <link.h>
1920
#include <linux/limits.h>
20-
#include <set>
2121
#include <stdio.h>
2222
#include <stdlib.h>
2323
#include <string.h>
24+
#include <sys/auxv.h>
2425
#include <sys/mman.h>
2526
#include <sys/stat.h>
2627
#include <sys/types.h>
28+
#include <unordered_map>
29+
#include <unordered_set>
2730

2831
// make sure lseek will use 64 bits offset
2932
#define _FILE_OFFSET_BITS 64
@@ -481,13 +484,19 @@ void ElfParser::addRelocationSymbols(ElfSection *reltab, const char *plt) {
481484
}
482485
}
483486

487+
struct SharedLibrary {
488+
char* file;
489+
const char* map_start;
490+
const char* map_end;
491+
const char* image_base;
492+
};
493+
484494
Mutex Symbols::_parse_lock;
485495
bool Symbols::_have_kernel_symbols = false;
486-
static std::set<const void *> _parsed_libraries;
487-
static std::set<u64> _parsed_inodes;
496+
bool Symbols::_libs_limit_reported = false;
497+
static std::unordered_set<u64> _parsed_inodes;
488498

489499
void Symbols::clearParsingCaches() {
490-
_parsed_libraries.clear();
491500
_parsed_inodes.clear();
492501
}
493502

@@ -531,22 +540,20 @@ void Symbols::parseKernelSymbols(CodeCache *cc) {
531540
fclose(f);
532541
}
533542

534-
static int parseLibrariesCallback(struct dl_phdr_info *info, size_t size,
535-
void *data) {
543+
static void collectSharedLibraries(std::unordered_map<u64, SharedLibrary>& libs, int max_count) {
536544

537545
FILE *f = fopen("/proc/self/maps", "r");
538546
if (f == NULL) {
539-
return 1;
547+
return;
540548
}
541549

542-
CodeCacheArray *array = (CodeCacheArray *)data;
543550
const char *image_base = NULL;
544551
u64 last_inode = 0;
545552
char *str = NULL;
546553
size_t str_size = 0;
547554
ssize_t len;
548555

549-
while ((len = getline(&str, &str_size, f)) > 0) {
556+
while (max_count > 0 && (len = getline(&str, &str_size, f)) > 0) {
550557
str[len - 1] = 0;
551558
MemoryMapDesc map(str);
552559
if (!map.isReadable() || map.file() == NULL || map.file()[0] == 0) {
@@ -557,63 +564,46 @@ static int parseLibrariesCallback(struct dl_phdr_info *info, size_t size,
557564
continue;
558565
}
559566

560-
const char *map_start = map.addr();
561-
unsigned long map_offs = map.offs();
562-
563-
if (map_offs == 0) {
564-
image_base = map_start;
565-
last_inode = u64(map.dev()) << 32 | map.inode();
567+
u64 inode = u64(map.dev()) << 32 | map.inode();
568+
if (_parsed_inodes.find(inode) != _parsed_inodes.end()) {
569+
continue; // shared object is already parsed
566570
}
567-
568-
if (!map.isExecutable() || !_parsed_libraries.insert(map_start).second) {
569-
// Not an executable segment or it has been already parsed
570-
continue;
571+
if (inode == 0 && strcmp(map.file(), "[vdso]") != 0) {
572+
continue; // all shared libraries have inode, except vDSO
571573
}
572574

573-
int count = array->count();
574-
if (count >= MAX_NATIVE_LIBS) {
575-
break;
575+
const char* map_start = map.addr();
576+
const char *map_end = map.end();
577+
if (inode != last_inode && map.offs() == 0) {
578+
image_base = map_start;
579+
last_inode = inode;
576580
}
577581

578-
const char *map_end = map.end();
579-
// Do not try to parse pseudofiles like anon_inode:name, /memfd:name
580-
if (strchr(map.file(), ':') == NULL) {
581-
CodeCache *cc = new CodeCache(map.file(), count, false, map_start, map_end);
582-
TEST_LOG("Procesing library: %s", map.file());
583-
u64 inode = u64(map.dev()) << 32 | map.inode();
584-
if (inode != 0) {
585-
// Do not parse the same executable twice, e.g. on Alpine Linux
586-
if (_parsed_inodes.insert(inode).second) {
587-
if (inode == last_inode) {
588-
// If last_inode is set, image_base is known to be valid and
589-
// readable
590-
ElfParser::parseFile(cc, image_base, map.file(), true);
591-
// Parse program headers after the file to ensure debug symbols are
592-
// parsed first
593-
ElfParser::parseProgramHeaders(cc, image_base, map_end, MUSL);
594-
} else if ((unsigned long)map_start > map_offs) {
595-
// Unlikely case when image_base has not been found.
596-
// Be careful: executable file is not always ELF, e.g. classes.jsa
597-
ElfParser::parseFile(cc, map_start - map_offs, map.file(), true);
598-
}
582+
if (map.isExecutable()) {
583+
SharedLibrary& lib = libs[inode];
584+
if (lib.file == nullptr) {
585+
lib.file = strdup(map.file());
586+
lib.map_start = map_start;
587+
lib.map_end = map_end;
588+
lib.image_base = inode == last_inode ? image_base : NULL;
589+
max_count--;
590+
} else {
591+
// The same library may have multiple executable segments mapped
592+
lib.map_end = map_end;
599593
}
600-
} else if (strcmp(map.file(), "[vdso]") == 0) {
601-
ElfParser::parseProgramHeaders(cc, map_start, map_end, true);
602-
}
603-
604-
cc->sort();
605-
array->add(cc);
606594
}
607595
}
608-
609596
free(str);
610597
fclose(f);
611-
return 1; // stop at first iteration
612598
}
613599

614600
void Symbols::parseLibraries(CodeCacheArray *array, bool kernel_symbols) {
615601
MutexLocker ml(_parse_lock);
616602

603+
if (array->count() >= MAX_NATIVE_LIBS) {
604+
return;
605+
}
606+
617607
if (kernel_symbols && !haveKernelSymbols()) {
618608
CodeCache *cc = new CodeCache("[kernel]");
619609
parseKernelSymbols(cc);
@@ -625,12 +615,48 @@ void Symbols::parseLibraries(CodeCacheArray *array, bool kernel_symbols) {
625615
delete cc;
626616
}
627617
}
618+
std::unordered_map<u64, SharedLibrary> libs;
619+
collectSharedLibraries(libs, MAX_NATIVE_LIBS - array->count());
620+
621+
for (auto& it : libs) {
622+
u64 inode = it.first;
623+
_parsed_inodes.insert(inode);
624+
625+
SharedLibrary& lib = it.second;
626+
CodeCache* cc = new CodeCache(lib.file, array->count(), false, lib.map_start, lib.map_end, lib.image_base);
627+
628+
// Strip " (deleted)" suffix so that removed library can be reopened
629+
size_t len = strlen(lib.file);
630+
if (len > 10 && strcmp(lib.file + len - 10, " (deleted)") == 0) {
631+
lib.file[len - 10] = 0;
632+
}
633+
634+
if (strcmp(lib.file, "[vdso]") == 0) {
635+
ElfParser::parseProgramHeaders(cc, lib.map_start, lib.map_end, true);
636+
} else if (lib.image_base == NULL) {
637+
// Unlikely case when image base has not been found: not safe to access program headers.
638+
// Be careful: executable file is not always ELF, e.g. classes.jsa
639+
ElfParser::parseFile(cc, lib.map_start, lib.file, true);
640+
} else {
641+
// Parse debug symbols first
642+
ElfParser::parseFile(cc, lib.image_base, lib.file, true);
643+
644+
UnloadProtection handle(cc);
645+
if (handle.isValid()) {
646+
ElfParser::parseProgramHeaders(cc, lib.image_base, lib.map_end, MUSL);
647+
}
648+
}
649+
650+
free(lib.file);
628651

629-
// In glibc, dl_iterate_phdr() holds dl_load_write_lock, therefore preventing
630-
// concurrent loading and unloading of shared libraries.
631-
// Without it, we may access memory of a library that is being unloaded.
632-
dl_iterate_phdr(parseLibrariesCallback, array);
633-
TEST_LOG("Parsed %d libraries", array->count());
652+
cc->sort();
653+
array->add(cc);
654+
}
655+
656+
if (array->count() >= MAX_NATIVE_LIBS && !_libs_limit_reported) {
657+
Log::warn("Number of parsed libraries reached the limit of %d", MAX_NATIVE_LIBS);
658+
_libs_limit_reported = true;
659+
}
634660
}
635661

636662
bool Symbols::isRootSymbol(const void* address) {
@@ -642,4 +668,64 @@ bool Symbols::isRootSymbol(const void* address) {
642668
return false;
643669
}
644670

671+
// Check that the base address of the shared object has not changed
672+
static bool verifyBaseAddress(const CodeCache* cc, void* lib_handle) {
673+
Dl_info dl_info;
674+
struct link_map* map;
675+
676+
if (dlinfo(lib_handle, RTLD_DI_LINKMAP, &map) != 0 || dladdr(map->l_ld, &dl_info) == 0) {
677+
return false;
678+
}
679+
680+
return cc->imageBase() == (const char*)dl_info.dli_fbase;
681+
}
682+
683+
static const void* getMainPhdr() {
684+
void* main_phdr = NULL;
685+
dl_iterate_phdr([](struct dl_phdr_info* info, size_t size, void* data) {
686+
*(const void**)data = info->dlpi_phdr;
687+
return 1;
688+
}, &main_phdr);
689+
return main_phdr;
690+
}
691+
692+
static const void* _main_phdr = getMainPhdr();
693+
static const char* _ld_base = (const char*)getauxval(AT_BASE);
694+
695+
static bool isMainExecutable(const char* image_base, const void* map_end) {
696+
return _main_phdr != NULL && _main_phdr >= image_base && _main_phdr < map_end;
697+
}
698+
699+
static bool isLoader(const char* image_base) {
700+
return _ld_base == image_base;
701+
}
702+
703+
UnloadProtection::UnloadProtection(const CodeCache *cc) {
704+
if (MUSL || isMainExecutable(cc->imageBase(), cc->maxAddress()) || isLoader(cc->imageBase())) {
705+
_lib_handle = NULL;
706+
_valid = true;
707+
return;
708+
}
709+
710+
// dlopen() can reopen previously loaded libraries even if the underlying file has been deleted
711+
const char* stripped_name = cc->name();
712+
size_t name_len = strlen(stripped_name);
713+
if (name_len > 10 && strcmp(stripped_name + name_len - 10, " (deleted)") == 0) {
714+
char* buf = (char*) alloca(name_len - 9);
715+
*stpncpy(buf, stripped_name, name_len - 10) = 0;
716+
stripped_name = buf;
717+
}
718+
719+
// Protect library from unloading while parsing in-memory ELF program headers.
720+
// Also, dlopen() ensures the library is fully loaded.
721+
_lib_handle = dlopen(stripped_name, RTLD_LAZY | RTLD_NOLOAD);
722+
_valid = _lib_handle != NULL && verifyBaseAddress(cc, _lib_handle);
723+
}
724+
725+
UnloadProtection::~UnloadProtection() {
726+
if (_lib_handle != NULL) {
727+
dlclose(_lib_handle);
728+
}
729+
}
730+
645731
#endif // __linux__

0 commit comments

Comments
 (0)