Files
async-profiler/src/symbols_linux.cpp
Andrei Pangin 8fee26589d Merge branch 'master' into bpfclient
# Conflicts:
#	Makefile
#	src/arguments.cpp
#	src/arguments.h
#	src/flightRecorder.cpp
2023-02-14 02:23:33 +00:00

631 lines
20 KiB
C++

/*
* Copyright 2017 Andrei Pangin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef __linux__
#include <set>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <elf.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/limits.h>
#include "symbols.h"
#include "dwarf.h"
#include "fdtransferClient.h"
#include "log.h"
class SymbolDesc {
private:
const char* _addr;
const char* _desc;
public:
SymbolDesc(const char* s) {
_addr = s;
_desc = strchr(_addr, ' ');
}
const char* addr() { return (const char*)strtoul(_addr, NULL, 16); }
char type() { return _desc != NULL ? _desc[1] : 0; }
const char* name() { return _desc + 3; }
};
class MemoryMapDesc {
private:
const char* _addr;
const char* _end;
const char* _perm;
const char* _offs;
const char* _dev;
const char* _inode;
const char* _file;
public:
MemoryMapDesc(const char* s) {
_addr = s;
_end = strchr(_addr, '-') + 1;
_perm = strchr(_end, ' ') + 1;
_offs = strchr(_perm, ' ') + 1;
_dev = strchr(_offs, ' ') + 1;
_inode = strchr(_dev, ' ') + 1;
_file = strchr(_inode, ' ');
if (_file != NULL) {
while (*_file == ' ') _file++;
}
}
const char* file() { return _file; }
bool isReadable() { return _perm[0] == 'r'; }
bool isExecutable() { return _perm[2] == 'x'; }
const char* addr() { return (const char*)strtoul(_addr, NULL, 16); }
const char* end() { return (const char*)strtoul(_end, NULL, 16); }
unsigned long offs() { return strtoul(_offs, NULL, 16); }
unsigned long inode() { return strtoul(_inode, NULL, 10); }
unsigned long dev() {
char* colon;
unsigned long major = strtoul(_dev, &colon, 16);
unsigned long minor = strtoul(colon + 1, NULL, 16);
return major << 8 | minor;
}
};
#ifdef __LP64__
const unsigned char ELFCLASS_SUPPORTED = ELFCLASS64;
typedef Elf64_Ehdr ElfHeader;
typedef Elf64_Shdr ElfSection;
typedef Elf64_Phdr ElfProgramHeader;
typedef Elf64_Nhdr ElfNote;
typedef Elf64_Sym ElfSymbol;
typedef Elf64_Rel ElfRelocation;
typedef Elf64_Dyn ElfDyn;
#define ELF_R_TYPE ELF64_R_TYPE
#define ELF_R_SYM ELF64_R_SYM
#else
const unsigned char ELFCLASS_SUPPORTED = ELFCLASS32;
typedef Elf32_Ehdr ElfHeader;
typedef Elf32_Shdr ElfSection;
typedef Elf32_Phdr ElfProgramHeader;
typedef Elf32_Nhdr ElfNote;
typedef Elf32_Sym ElfSymbol;
typedef Elf32_Rel ElfRelocation;
typedef Elf32_Dyn ElfDyn;
#define ELF_R_TYPE ELF32_R_TYPE
#define ELF_R_SYM ELF32_R_SYM
#endif // __LP64__
#if defined(__x86_64__)
# define R_GLOB_DAT R_X86_64_GLOB_DAT
#elif defined(__i386__)
# define R_GLOB_DAT R_386_GLOB_DAT
#elif defined(__arm__) || defined(__thumb__)
# define R_GLOB_DAT R_ARM_GLOB_DAT
#elif defined(__aarch64__)
# define R_GLOB_DAT R_AARCH64_GLOB_DAT
#elif defined(__PPC64__)
# define R_GLOB_DAT R_PPC64_GLOB_DAT
#else
# error "Compiling on unsupported arch"
#endif
// GNU dynamic linker relocates pointers in the dynamic section, while musl doesn't.
// A tricky case is when we attach to a musl container from a glibc host.
#ifdef __musl__
# define DYN_PTR(ptr) (_base + (ptr))
#else
# define DYN_PTR(ptr) ((char*)(ptr) >= _base ? (char*)(ptr) : _base + (ptr))
#endif // __musl__
class ElfParser {
private:
CodeCache* _cc;
const char* _base;
const char* _file_name;
ElfHeader* _header;
const char* _sections;
const char* _vaddr_diff;
ElfParser(CodeCache* cc, const char* base, const void* addr, const char* file_name = NULL) {
_cc = cc;
_base = base;
_file_name = file_name;
_header = (ElfHeader*)addr;
_sections = (const char*)addr + _header->e_shoff;
}
bool validHeader() {
unsigned char* ident = _header->e_ident;
return ident[0] == 0x7f && ident[1] == 'E' && ident[2] == 'L' && ident[3] == 'F'
&& ident[4] == ELFCLASS_SUPPORTED && ident[5] == ELFDATA2LSB && ident[6] == EV_CURRENT
&& _header->e_shstrndx != SHN_UNDEF;
}
ElfSection* section(int index) {
return (ElfSection*)(_sections + index * _header->e_shentsize);
}
const char* at(ElfSection* section) {
return (const char*)_header + section->sh_offset;
}
const char* at(ElfProgramHeader* pheader) {
return _header->e_type == ET_EXEC ? (const char*)pheader->p_vaddr : _vaddr_diff + pheader->p_vaddr;
}
ElfSection* findSection(uint32_t type, const char* name);
ElfProgramHeader* findProgramHeader(uint32_t type);
void calcVirtualLoadAddress();
void parseDynamicSection();
void parseDwarfInfo();
void loadSymbols(bool use_debug);
bool loadSymbolsUsingBuildId();
bool loadSymbolsUsingDebugLink();
void loadSymbolTable(ElfSection* symtab);
void addRelocationSymbols(ElfSection* reltab, const char* plt);
public:
static void parseProgramHeaders(CodeCache* cc, const char* base, const char* end);
static bool parseFile(CodeCache* cc, const char* base, const char* file_name, bool use_debug);
static void parseMem(CodeCache* cc, const char* base);
};
ElfSection* ElfParser::findSection(uint32_t type, const char* name) {
const char* strtab = at(section(_header->e_shstrndx));
for (int i = 0; i < _header->e_shnum; i++) {
ElfSection* section = this->section(i);
if (section->sh_type == type && section->sh_name != 0) {
if (strcmp(strtab + section->sh_name, name) == 0) {
return section;
}
}
}
return NULL;
}
ElfProgramHeader* ElfParser::findProgramHeader(uint32_t type) {
const char* pheaders = (const char*)_header + _header->e_phoff;
for (int i = 0; i < _header->e_phnum; i++) {
ElfProgramHeader* pheader = (ElfProgramHeader*)(pheaders + i * _header->e_phentsize);
if (pheader->p_type == type) {
return pheader;
}
}
return NULL;
}
bool ElfParser::parseFile(CodeCache* cc, const char* base, const char* file_name, bool use_debug) {
int fd = open(file_name, O_RDONLY);
if (fd == -1) {
return false;
}
size_t length = (size_t)lseek64(fd, 0, SEEK_END);
void* addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (addr == MAP_FAILED) {
Log::warn("Could not parse symbols from %s: %s", file_name, strerror(errno));
} else {
ElfParser elf(cc, base, addr, file_name);
if (elf.validHeader()) {
elf.loadSymbols(use_debug);
}
munmap(addr, length);
}
return true;
}
void ElfParser::parseMem(CodeCache* cc, const char* base) {
ElfParser elf(cc, base, base);
if (elf.validHeader()) {
elf.loadSymbols(false);
}
}
void ElfParser::parseProgramHeaders(CodeCache* cc, const char* base, const char* end) {
ElfParser elf(cc, base, base);
if (elf.validHeader() && base + elf._header->e_phoff < end) {
cc->setTextBase(base);
elf.calcVirtualLoadAddress();
elf.parseDynamicSection();
elf.parseDwarfInfo();
}
}
void ElfParser::calcVirtualLoadAddress() {
// Find a difference between the virtual load address (often zero) and the actual DSO base
const char* pheaders = (const char*)_header + _header->e_phoff;
for (int i = 0; i < _header->e_phnum; i++) {
ElfProgramHeader* pheader = (ElfProgramHeader*)(pheaders + i * _header->e_phentsize);
if (pheader->p_type == PT_LOAD) {
_vaddr_diff = _base - pheader->p_vaddr;
return;
}
}
_vaddr_diff = _base;
}
void ElfParser::parseDynamicSection() {
ElfProgramHeader* dynamic = findProgramHeader(PT_DYNAMIC);
if (dynamic != NULL) {
void** got_start = NULL;
size_t pltrelsz = 0;
char* rel = NULL;
size_t relsz = 0;
size_t relent = 0;
size_t relcount = 0;
const char* dyn_start = at(dynamic);
const char* dyn_end = dyn_start + dynamic->p_memsz;
for (ElfDyn* dyn = (ElfDyn*)dyn_start; dyn < (ElfDyn*)dyn_end; dyn++) {
switch (dyn->d_tag) {
case DT_PLTGOT:
got_start = (void**)DYN_PTR(dyn->d_un.d_ptr) + 3;
break;
case DT_PLTRELSZ:
pltrelsz = dyn->d_un.d_val;
break;
case DT_RELA:
case DT_REL:
rel = (char*)DYN_PTR(dyn->d_un.d_ptr);
break;
case DT_RELASZ:
case DT_RELSZ:
relsz = dyn->d_un.d_val;
break;
case DT_RELAENT:
case DT_RELENT:
relent = dyn->d_un.d_val;
break;
case DT_RELACOUNT:
case DT_RELCOUNT:
relcount = dyn->d_un.d_val;
break;
}
}
if (relent != 0) {
if (pltrelsz != 0 && got_start != NULL) {
// The number of entries in .got.plt section matches the number of entries in .rela.plt
_cc->setGlobalOffsetTable(got_start, got_start + pltrelsz / relent, false);
} else if (rel != NULL && relsz != 0) {
// RELRO technique: .got.plt has been merged into .got and made read-only.
// Find .got end from the highest relocation address.
void** min_addr = (void**)-1;
void** max_addr = (void**)0;
for (size_t offs = relcount * relent; offs < relsz; offs += relent) {
ElfRelocation* r = (ElfRelocation*)(rel + offs);
if (ELF_R_TYPE(r->r_info) == R_GLOB_DAT) {
void** addr = (void**)(_base + r->r_offset);
if (addr < min_addr) min_addr = addr;
if (addr > max_addr) max_addr = addr;
}
}
if (got_start == NULL) {
got_start = (void**)min_addr;
}
if (max_addr >= got_start) {
_cc->setGlobalOffsetTable(got_start, max_addr + 1, false);
}
}
}
}
}
void ElfParser::parseDwarfInfo() {
if (!DWARF_SUPPORTED) return;
ElfProgramHeader* eh_frame_hdr = findProgramHeader(PT_GNU_EH_FRAME);
if (eh_frame_hdr != NULL) {
DwarfParser dwarf(_cc->name(), _base, at(eh_frame_hdr));
_cc->setDwarfTable(dwarf.table(), dwarf.count());
}
}
void ElfParser::loadSymbols(bool use_debug) {
// Look for debug symbols in the original .so
ElfSection* section = findSection(SHT_SYMTAB, ".symtab");
if (section != NULL) {
loadSymbolTable(section);
_cc->setDebugSymbols(true);
goto loaded;
}
// Try to load symbols from an external debuginfo library
if (use_debug) {
if (loadSymbolsUsingBuildId() || loadSymbolsUsingDebugLink()) {
goto loaded;
}
}
// If everything else fails, load only exported symbols
section = findSection(SHT_DYNSYM, ".dynsym");
if (section != NULL) {
loadSymbolTable(section);
}
loaded:
if (use_debug) {
// Synthesize names for PLT stubs
ElfSection* plt = findSection(SHT_PROGBITS, ".plt");
ElfSection* reltab = findSection(SHT_RELA, ".rela.plt");
if (reltab == NULL) {
reltab = findSection(SHT_REL, ".rel.plt");
}
if (plt != NULL && reltab != NULL) {
addRelocationSymbols(reltab, _base + plt->sh_offset + PLT_HEADER_SIZE);
}
}
}
// Load symbols from /usr/lib/debug/.build-id/ab/cdef1234.debug, where abcdef1234 is Build ID
bool ElfParser::loadSymbolsUsingBuildId() {
ElfSection* section = findSection(SHT_NOTE, ".note.gnu.build-id");
if (section == NULL || section->sh_size <= 16) {
return false;
}
ElfNote* note = (ElfNote*)at(section);
if (note->n_namesz != 4 || note->n_descsz < 2 || note->n_descsz > 64) {
return false;
}
const char* build_id = (const char*)note + sizeof(*note) + 4;
int build_id_len = note->n_descsz;
char path[PATH_MAX];
char* p = path + sprintf(path, "/usr/lib/debug/.build-id/%02hhx/", build_id[0]);
for (int i = 1; i < build_id_len; i++) {
p += sprintf(p, "%02hhx", build_id[i]);
}
strcpy(p, ".debug");
return parseFile(_cc, _base, path, false);
}
// Look for debuginfo file specified in .gnu_debuglink section
bool ElfParser::loadSymbolsUsingDebugLink() {
ElfSection* section = findSection(SHT_PROGBITS, ".gnu_debuglink");
if (section == NULL || section->sh_size <= 4) {
return false;
}
const char* basename = strrchr(_file_name, '/');
if (basename == NULL) {
return false;
}
char* dirname = strndup(_file_name, basename - _file_name);
if (dirname == NULL) {
return false;
}
const char* debuglink = at(section);
char path[PATH_MAX];
bool result = false;
// 1. /path/to/libjvm.so.debug
if (strcmp(debuglink, basename + 1) != 0 &&
snprintf(path, PATH_MAX, "%s/%s", dirname, debuglink) < PATH_MAX) {
result = parseFile(_cc, _base, path, false);
}
// 2. /path/to/.debug/libjvm.so.debug
if (!result && snprintf(path, PATH_MAX, "%s/.debug/%s", dirname, debuglink) < PATH_MAX) {
result = parseFile(_cc, _base, path, false);
}
// 3. /usr/lib/debug/path/to/libjvm.so.debug
if (!result && snprintf(path, PATH_MAX, "/usr/lib/debug%s/%s", dirname, debuglink) < PATH_MAX) {
result = parseFile(_cc, _base, path, false);
}
free(dirname);
return result;
}
void ElfParser::loadSymbolTable(ElfSection* symtab) {
ElfSection* strtab = section(symtab->sh_link);
const char* strings = at(strtab);
const char* symbols = at(symtab);
const char* symbols_end = symbols + symtab->sh_size;
for (; symbols < symbols_end; symbols += symtab->sh_entsize) {
ElfSymbol* sym = (ElfSymbol*)symbols;
if (sym->st_name != 0 && sym->st_value != 0) {
// Skip special AArch64 mapping symbols: $x and $d
if (sym->st_size != 0 || sym->st_info != 0 || strings[sym->st_name] != '$') {
_cc->add(_base + sym->st_value, (int)sym->st_size, strings + sym->st_name);
}
}
}
}
void ElfParser::addRelocationSymbols(ElfSection* reltab, const char* plt) {
ElfSection* symtab = section(reltab->sh_link);
const char* symbols = at(symtab);
ElfSection* strtab = section(symtab->sh_link);
const char* strings = at(strtab);
const char* relocations = at(reltab);
const char* relocations_end = relocations + reltab->sh_size;
for (; relocations < relocations_end; relocations += reltab->sh_entsize) {
ElfRelocation* r = (ElfRelocation*)relocations;
ElfSymbol* sym = (ElfSymbol*)(symbols + ELF_R_SYM(r->r_info) * symtab->sh_entsize);
char name[256];
if (sym->st_name == 0) {
strcpy(name, "@plt");
} else {
const char* sym_name = strings + sym->st_name;
snprintf(name, sizeof(name), "%s%cplt", sym_name, sym_name[0] == '_' && sym_name[1] == 'Z' ? '.' : '@');
name[sizeof(name) - 1] = 0;
}
_cc->add(plt, PLT_ENTRY_SIZE, name);
plt += PLT_ENTRY_SIZE;
}
}
Mutex Symbols::_parse_lock;
bool Symbols::_have_kernel_symbols = false;
static std::set<const void*> _parsed_libraries;
static std::set<u64> _parsed_inodes;
void Symbols::parseKernelSymbols(CodeCache* cc) {
int fd;
if (false && FdTransferClient::hasPeer()) {
fd = FdTransferClient::requestKallsymsFd();
} else {
fd = open("/proc/kallsyms", O_RDONLY);
}
if (fd == -1) {
Log::warn("open(\"/proc/kallsyms\"): %s", strerror(errno));
return;
}
FILE* f = fdopen(fd, "r");
if (f == NULL) {
Log::warn("fdopen(): %s", strerror(errno));
close(fd);
return;
}
char str[256];
while (fgets(str, sizeof(str) - 8, f) != NULL) {
size_t len = strlen(str) - 1; // trim the '\n'
strcpy(str + len, "_[k]");
SymbolDesc symbol(str);
char type = symbol.type();
if (type == 'T' || type == 't' || type == 'W' || type == 'w') {
const char* addr = symbol.addr();
if (addr != NULL) {
if (!_have_kernel_symbols) {
if (strncmp(symbol.name(), "__LOAD_PHYSICAL_ADDR", 20) == 0 ||
strncmp(symbol.name(), "phys_startup", 12) == 0) {
continue;
}
_have_kernel_symbols = true;
}
cc->add(addr, 0, symbol.name());
}
}
}
fclose(f);
}
void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) {
MutexLocker ml(_parse_lock);
if (kernel_symbols && !haveKernelSymbols()) {
CodeCache* cc = new CodeCache("[kernel]");
parseKernelSymbols(cc);
if (haveKernelSymbols()) {
cc->sort();
array->add(cc);
} else {
delete cc;
}
}
FILE* f = fopen("/proc/self/maps", "r");
if (f == NULL) {
return;
}
const char* last_readable_base = NULL;
const char* image_end = NULL;
char* str = NULL;
size_t str_size = 0;
ssize_t len;
while ((len = getline(&str, &str_size, f)) > 0) {
str[len - 1] = 0;
MemoryMapDesc map(str);
if (!map.isReadable() || map.file() == NULL || map.file()[0] == 0) {
continue;
}
const char* image_base = map.addr();
if (image_base != image_end) last_readable_base = image_base;
image_end = map.end();
if (map.isExecutable()) {
if (!_parsed_libraries.insert(image_base).second) {
continue; // the library was already parsed
}
int count = array->count();
if (count >= MAX_NATIVE_LIBS) {
break;
}
CodeCache* cc = new CodeCache(map.file(), count, image_base, image_end);
// Do not try to parse pseudofiles like anon_inode:name, /memfd:name
if (strchr(map.file(), ':') == NULL) {
unsigned long inode = map.inode();
if (inode != 0) {
// Do not parse the same executable twice, e.g. on Alpine Linux
if (_parsed_inodes.insert(u64(map.dev()) << 32 | inode).second) {
// Be careful: executable file is not always ELF, e.g. classes.jsa
unsigned long offs = map.offs();
if ((unsigned long)image_base > offs && (image_base -= offs) >= last_readable_base) {
ElfParser::parseProgramHeaders(cc, image_base, image_end);
}
ElfParser::parseFile(cc, image_base, map.file(), true);
}
} else if (strcmp(map.file(), "[vdso]") == 0) {
ElfParser::parseMem(cc, image_base);
}
}
cc->sort();
array->add(cc);
}
}
free(str);
fclose(f);
}
#endif // __linux__