diff options
author | Milian Wolff <milian.wolff@kdab.com> | 2022-05-28 22:16:31 +0200 |
---|---|---|
committer | Milian Wolff <milian.wolff@kdab.com> | 2022-06-08 20:18:12 +0000 |
commit | ba1d0501e3f3a3a26d7f2af1a89f25b5495e4709 (patch) | |
tree | b30419ef710a1e4ce81b1dbeaa7557b758660c50 | |
parent | f8457c36810dc8cfc2acf5687b704a3c71874c80 (diff) |
Cleanup: move unwinding code to perfunwind
Instead, pass the thread callbacks to PerfSymbolTable::attachDwfl.
This way all the code relevant for unwinding is centralized in the
perfunwind file and not split across the symbol table and unwind
files.
Change-Id: I6f2dcecef0949aa6370b1e6a279132cfd5012830
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r-- | app/perfsymboltable.cpp | 144 | ||||
-rw-r--r-- | app/perfsymboltable.h | 2 | ||||
-rw-r--r-- | app/perfunwind.cpp | 141 |
3 files changed, 142 insertions, 145 deletions
diff --git a/app/perfsymboltable.cpp b/app/perfsymboltable.cpp index 63eef45..55efb16 100644 --- a/app/perfsymboltable.cpp +++ b/app/perfsymboltable.cpp @@ -32,9 +32,6 @@ #include <QDir> #include <QStack> -#include <tuple> -#include <cstring> - #include <dwarf.h> PerfSymbolTable::PerfSymbolTable(qint32 pid, Dwfl_Callbacks *callbacks, PerfUnwind *parent) : @@ -66,143 +63,6 @@ PerfSymbolTable::~PerfSymbolTable() dwfl_end(m_dwfl); } -static pid_t nextThread(Dwfl *dwfl, void *arg, void **threadArg) -{ - /* Stop after first thread. */ - if (*threadArg != nullptr) - return 0; - - *threadArg = arg; - return dwfl_pid(dwfl); -} - -static void *memcpyTarget(Dwarf_Word *result, int wordWidth) -{ - if (wordWidth == 4) - return (uint32_t *)result; - - Q_ASSERT(wordWidth == 8); - return result; -} - -static void doMemcpy(Dwarf_Word *result, const void *src, int wordWidth) -{ - Q_ASSERT(wordWidth > 0); - *result = 0; // initialize, as we might only overwrite half of it - std::memcpy(memcpyTarget(result, wordWidth), src, static_cast<size_t>(wordWidth)); -} - -static quint64 registerAbi(const PerfRecordSample *sample) -{ - const quint64 abi = sample->registerAbi(); - Q_ASSERT(abi > 0); // ABI 0 means "no registers" - we shouldn't unwind in this case. - return abi - 1; -} - -static bool accessDsoMem(const PerfUnwind::UnwindInfo *ui, Dwarf_Addr addr, - Dwarf_Word *result, int wordWidth) -{ - Q_ASSERT(wordWidth > 0); - // TODO: Take the pgoff into account? Or does elf_getdata do that already? - auto mod = ui->unwind->symbolTable(ui->sample->pid())->module(addr); - if (!mod) - return false; - - Dwarf_Addr bias; - Elf_Scn *section = dwfl_module_address_section(mod, &addr, &bias); - - if (section) { - Elf_Data *data = elf_getdata(section, nullptr); - if (data && data->d_buf && data->d_size > addr) { - doMemcpy(result, static_cast<char *>(data->d_buf) + addr, wordWidth); - return true; - } - } - - return false; -} - -static bool memoryRead(Dwfl *, Dwarf_Addr addr, Dwarf_Word *result, void *arg) -{ - PerfUnwind::UnwindInfo *ui = static_cast<PerfUnwind::UnwindInfo *>(arg); - const int wordWidth = - PerfRegisterInfo::s_wordWidth[ui->unwind->architecture()][registerAbi(ui->sample)]; - - /* Check overflow. */ - if (addr + sizeof(Dwarf_Word) < addr) { - qDebug() << "Invalid memory read requested by dwfl" << Qt::hex << addr; - ui->firstGuessedFrame = ui->frames.length(); - return false; - } - - const QByteArray &stack = ui->sample->userStack(); - - quint64 start = ui->sample->registerValue( - PerfRegisterInfo::s_perfSp[ui->unwind->architecture()]); - Q_ASSERT(stack.size() >= 0); - quint64 end = start + static_cast<quint64>(stack.size()); - - if (addr < start || addr + sizeof(Dwarf_Word) > end) { - // not stack, try reading from ELF - if (ui->unwind->ipIsInKernelSpace(addr)) { - // DWARF unwinding is not done for the kernel - qWarning() << "DWARF unwind tried to access kernel space" << Qt::hex << addr; - return false; - } - if (!accessDsoMem(ui, addr, result, wordWidth)) { - ui->firstGuessedFrame = ui->frames.length(); - const QHash<quint64, Dwarf_Word> &stackValues = ui->stackValues[ui->sample->pid()]; - auto it = stackValues.find(addr); - if (it == stackValues.end()) { - return false; - } else { - *result = *it; - } - } - } else { - doMemcpy(result, &(stack.data()[addr - start]), wordWidth); - ui->stackValues[ui->sample->pid()][addr] = *result; - } - return true; -} - -static bool setInitialRegisters(Dwfl_Thread *thread, void *arg) -{ - const PerfUnwind::UnwindInfo *ui = static_cast<PerfUnwind::UnwindInfo *>(arg); - const quint64 abi = registerAbi(ui->sample); - const uint architecture = ui->unwind->architecture(); - const int numRegs = PerfRegisterInfo::s_numRegisters[architecture][abi]; - Q_ASSERT(numRegs >= 0); - QVarLengthArray<Dwarf_Word, 64> dwarfRegs(numRegs); - for (int i = 0; i < numRegs; ++i) { - dwarfRegs[i] = ui->sample->registerValue( - PerfRegisterInfo::s_perfToDwarf[architecture][abi][i]); - } - - // Go one frame up to get the rest of the stack at interworking veneers. - if (ui->isInterworking) { - dwarfRegs[static_cast<int>(PerfRegisterInfo::s_dwarfIp[architecture][abi])] = - dwarfRegs[static_cast<int>(PerfRegisterInfo::s_dwarfLr[architecture][abi])]; - } - - int dummyBegin = PerfRegisterInfo::s_dummyRegisters[architecture][0]; - int dummyNum = PerfRegisterInfo::s_dummyRegisters[architecture][1] - dummyBegin; - - if (dummyNum > 0) { - QVarLengthArray<Dwarf_Word, 64> dummyRegs(dummyNum); - std::memset(dummyRegs.data(), 0, static_cast<size_t>(dummyNum) * sizeof(Dwarf_Word)); - if (!dwfl_thread_state_registers(thread, dummyBegin, static_cast<uint>(dummyNum), - dummyRegs.data())) - return false; - } - - return dwfl_thread_state_registers(thread, 0, static_cast<uint>(numRegs), dwarfRegs.data()); -} - -static const Dwfl_Thread_Callbacks threadCallbacks = { - nextThread, nullptr, memoryRead, setInitialRegisters, nullptr, nullptr -}; - static bool findInExtraPath(QFileInfo &path, const QString &fileName) { path.setFile(path.absoluteFilePath() + QDir::separator() + fileName); @@ -976,7 +836,7 @@ bool PerfSymbolTable::containsAddress(quint64 address) const return m_elfs.isAddressInRange(address); } -Dwfl *PerfSymbolTable::attachDwfl(void *arg) +Dwfl *PerfSymbolTable::attachDwfl(const Dwfl_Thread_Callbacks *callbacks, void *arg) { if (static_cast<pid_t>(m_pid) == dwfl_pid(m_dwfl)) return m_dwfl; // Already attached, nothing to do @@ -991,7 +851,7 @@ Dwfl *PerfSymbolTable::attachDwfl(void *arg) if (!hasSampleRegsUser || !hasSampleStackUser) return nullptr; - if (!dwfl_attach_state(m_dwfl, m_firstElf.elf(), m_pid, &threadCallbacks, arg)) { + if (!dwfl_attach_state(m_dwfl, m_firstElf.elf(), m_pid, callbacks, arg)) { qWarning() << m_pid << "failed to attach state" << dwfl_errmsg(dwfl_errno()); return nullptr; } diff --git a/app/perfsymboltable.h b/app/perfsymboltable.h index 49bf852..66ba74e 100644 --- a/app/perfsymboltable.h +++ b/app/perfsymboltable.h @@ -72,7 +72,7 @@ public: void updatePerfMap(); bool containsAddress(quint64 address) const; - Dwfl *attachDwfl(void *arg); + Dwfl *attachDwfl(const Dwfl_Thread_Callbacks *callbacks, void *arg); void clearCache(); bool cacheIsDirty() const { return m_cacheIsDirty; } diff --git a/app/perfunwind.cpp b/app/perfunwind.cpp index 2fa2587..2d3e9a3 100644 --- a/app/perfunwind.cpp +++ b/app/perfunwind.cpp @@ -49,6 +49,143 @@ bool operator==(const PerfUnwind::Location &a, const PerfUnwind::Location &b) && a.column == b.column; } +static pid_t nextThread(Dwfl *dwfl, void *arg, void **threadArg) +{ + /* Stop after first thread. */ + if (*threadArg != nullptr) + return 0; + + *threadArg = arg; + return dwfl_pid(dwfl); +} + +static void *memcpyTarget(Dwarf_Word *result, int wordWidth) +{ + if (wordWidth == 4) + return (uint32_t *)result; + + Q_ASSERT(wordWidth == 8); + return result; +} + +static void doMemcpy(Dwarf_Word *result, const void *src, int wordWidth) +{ + Q_ASSERT(wordWidth > 0); + *result = 0; // initialize, as we might only overwrite half of it + std::memcpy(memcpyTarget(result, wordWidth), src, static_cast<size_t>(wordWidth)); +} + +static quint64 registerAbi(const PerfRecordSample *sample) +{ + const quint64 abi = sample->registerAbi(); + Q_ASSERT(abi > 0); // ABI 0 means "no registers" - we shouldn't unwind in this case. + return abi - 1; +} + +static bool accessDsoMem(const PerfUnwind::UnwindInfo *ui, Dwarf_Addr addr, + Dwarf_Word *result, int wordWidth) +{ + Q_ASSERT(wordWidth > 0); + // TODO: Take the pgoff into account? Or does elf_getdata do that already? + auto mod = ui->unwind->symbolTable(ui->sample->pid())->module(addr); + if (!mod) + return false; + + Dwarf_Addr bias; + Elf_Scn *section = dwfl_module_address_section(mod, &addr, &bias); + + if (section) { + Elf_Data *data = elf_getdata(section, nullptr); + if (data && data->d_buf && data->d_size > addr) { + doMemcpy(result, static_cast<char *>(data->d_buf) + addr, wordWidth); + return true; + } + } + + return false; +} + +static bool memoryRead(Dwfl *, Dwarf_Addr addr, Dwarf_Word *result, void *arg) +{ + PerfUnwind::UnwindInfo *ui = static_cast<PerfUnwind::UnwindInfo *>(arg); + const int wordWidth = + PerfRegisterInfo::s_wordWidth[ui->unwind->architecture()][registerAbi(ui->sample)]; + + /* Check overflow. */ + if (addr + sizeof(Dwarf_Word) < addr) { + qDebug() << "Invalid memory read requested by dwfl" << Qt::hex << addr; + ui->firstGuessedFrame = ui->frames.length(); + return false; + } + + const QByteArray &stack = ui->sample->userStack(); + + quint64 start = ui->sample->registerValue( + PerfRegisterInfo::s_perfSp[ui->unwind->architecture()]); + Q_ASSERT(stack.size() >= 0); + quint64 end = start + static_cast<quint64>(stack.size()); + + if (addr < start || addr + sizeof(Dwarf_Word) > end) { + // not stack, try reading from ELF + if (ui->unwind->ipIsInKernelSpace(addr)) { + // DWARF unwinding is not done for the kernel + qWarning() << "DWARF unwind tried to access kernel space" << Qt::hex << addr; + return false; + } + if (!accessDsoMem(ui, addr, result, wordWidth)) { + ui->firstGuessedFrame = ui->frames.length(); + const QHash<quint64, Dwarf_Word> &stackValues = ui->stackValues[ui->sample->pid()]; + auto it = stackValues.find(addr); + if (it == stackValues.end()) { + return false; + } else { + *result = *it; + } + } + } else { + doMemcpy(result, &(stack.data()[addr - start]), wordWidth); + ui->stackValues[ui->sample->pid()][addr] = *result; + } + return true; +} + +static bool setInitialRegisters(Dwfl_Thread *thread, void *arg) +{ + const PerfUnwind::UnwindInfo *ui = static_cast<PerfUnwind::UnwindInfo *>(arg); + const quint64 abi = registerAbi(ui->sample); + const uint architecture = ui->unwind->architecture(); + const int numRegs = PerfRegisterInfo::s_numRegisters[architecture][abi]; + Q_ASSERT(numRegs >= 0); + QVarLengthArray<Dwarf_Word, 64> dwarfRegs(numRegs); + for (int i = 0; i < numRegs; ++i) { + dwarfRegs[i] = ui->sample->registerValue( + PerfRegisterInfo::s_perfToDwarf[architecture][abi][i]); + } + + // Go one frame up to get the rest of the stack at interworking veneers. + if (ui->isInterworking) { + dwarfRegs[static_cast<int>(PerfRegisterInfo::s_dwarfIp[architecture][abi])] = + dwarfRegs[static_cast<int>(PerfRegisterInfo::s_dwarfLr[architecture][abi])]; + } + + int dummyBegin = PerfRegisterInfo::s_dummyRegisters[architecture][0]; + int dummyNum = PerfRegisterInfo::s_dummyRegisters[architecture][1] - dummyBegin; + + if (dummyNum > 0) { + QVarLengthArray<Dwarf_Word, 64> dummyRegs(dummyNum); + std::memset(dummyRegs.data(), 0, static_cast<size_t>(dummyNum) * sizeof(Dwarf_Word)); + if (!dwfl_thread_state_registers(thread, dummyBegin, static_cast<uint>(dummyNum), + dummyRegs.data())) + return false; + } + + return dwfl_thread_state_registers(thread, 0, static_cast<uint>(numRegs), dwarfRegs.data()); +} + +static const Dwfl_Thread_Callbacks threadCallbacks = { + nextThread, nullptr, memoryRead, setInitialRegisters, nullptr, nullptr +}; + void PerfUnwind::Stats::addEventTime(quint64 time) { if (time && time < maxTime) @@ -410,7 +547,7 @@ static int frameCallback(Dwfl_Frame *state, void *arg) void PerfUnwind::unwindStack() { - Dwfl *dwfl = symbolTable(m_currentUnwind.sample->pid())->attachDwfl(&m_currentUnwind); + Dwfl *dwfl = symbolTable(m_currentUnwind.sample->pid())->attachDwfl(&threadCallbacks, &m_currentUnwind); if (!dwfl) return; @@ -442,7 +579,7 @@ void PerfUnwind::resolveCallchain() PerfSymbolTable *symbols = symbolTable(m_currentUnwind.sample->pid()); auto reportIp = [&](quint64 ip) -> bool { - symbols->attachDwfl(&m_currentUnwind); + symbols->attachDwfl(&threadCallbacks, &m_currentUnwind); m_currentUnwind.frames.append(symbols->lookupFrame(ip, isKernel, &m_currentUnwind.isInterworking)); return !symbols->cacheIsDirty(); |