Загрузка данных


// callgraph_onefile.cpp
//   g++ -std=c++17 -O0 -g -fno-omit-frame-pointer -rdynamic callgraph_onefile.cpp -ldl -o callgraph
// Результат:
//   callgraph.dot
//
// Просмотр через Graphviz:
//   dot -Tpng callgraph.dot -o callgraph.png

#include <cxxabi.h>
#include <dlfcn.h>

#include <algorithm>
#include <cstdlib>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

// Упрощаем защиту от inline-оптимизаций, чтобы стек был виден понятнее.
#define NOINLINE __attribute__((noinline))

struct FramePointer {
    FramePointer* previous;
    void* returnAddress;
};

std::string demangle(const char* name) {
    if (!name) return "unknown";

    int status = 0;
    char* realName = abi::__cxa_demangle(name, nullptr, nullptr, &status);

    if (status == 0 && realName) {
        std::string result(realName);
        std::free(realName);
        return result;
    }

    return name;
}

std::string symbolName(void* address) {
    Dl_info info{};

    if (dladdr(address, &info) && info.dli_sname) {
        return demangle(info.dli_sname);
    }

    std::ostringstream out;
    out << "addr_" << address;
    return out.str();
}

bool isServiceFunction(const std::string& name) {
    // Эти функции относятся к самому визуализатору, а не к демонстрационной программе.
    return name.rfind("addr_", 0) == 0 ||
           name.find("captureStack") != std::string::npos ||
           name.find("sampleCallStack") != std::string::npos ||
           name.find("symbolName") != std::string::npos ||
           name.find("demangle") != std::string::npos ||
           name.find("CallGraph") != std::string::npos ||
           name.find("isServiceFunction") != std::string::npos ||
           name.find("std::") != std::string::npos ||
           name.find("__libc") != std::string::npos ||
           name.find("_start") != std::string::npos;
}

std::vector<std::string> captureStack(std::size_t maxDepth = 64) {
    std::vector<std::string> stack;

#if defined(__x86_64__)
    FramePointer* frame = nullptr;

    // Читаем регистр rbp. Он указывает на текущий frame pointer.
    asm volatile("movq %%rbp, %0" : "=r"(frame));

    for (std::size_t depth = 0; frame && depth < maxDepth; ++depth) {
        FramePointer* next = frame->previous;
        void* returnAddress = frame->returnAddress;

        if (!returnAddress) break;

        std::string name = symbolName(returnAddress);
        if (!isServiceFunction(name)) {
            stack.push_back(name);
        }

        // На x86_64 стек обычно растёт вниз, поэтому предыдущий frame pointer должен находиться по большему адресу
        if (next <= frame) break;
        if (reinterpret_cast<std::uintptr_t>(next) - reinterpret_cast<std::uintptr_t>(frame) > 1024 * 1024) break;

        frame = next;
    }
#else
    std::cerr << "Frame pointer supports only x86_64 Linux.\n";
#endif

    // Сейчас порядок: текущая функция -> вызывающая -> ... -> main.
    // Для call graph удобнее root -> leaf.
    std::reverse(stack.begin(), stack.end());
    return stack;
}

class CallGraph {
public:
    void addStack(const std::vector<std::string>& stack) {
        if (stack.empty()) return;

        for (const auto& function : stack) {
            nodeHits[function]++;
        }

        for (std::size_t i = 1; i < stack.size(); ++i) {
            edges[{stack[i - 1], stack[i]}]++;
        }
    }

    void writeDot(const std::string& fileName) const {
        std::ofstream file(fileName);
        if (!file) {
            std::cerr << "Cannot write file: " << fileName << "\n";
            return;
        }

        file << "digraph CallGraph {\n";
        file << "    rankdir=TB;\n";
        file << "    node [shape=box, style=rounded];\n";
        file << "\n";

        for (const auto& [name, hits] : nodeHits) {
            file << "    \"" << escape(name) << "\" [label=\"" << escape(name)
                 << "\\nhits: " << hits << "\"];\n";
        }

        file << "\n";

        for (const auto& [edge, count] : edges) {
            file << "    \"" << escape(edge.first) << "\" -> \"" << escape(edge.second)
                 << "\" [label=\"" << count << "\"];\n";
        }

        file << "}\n";
    }

private:
    std::map<std::string, int> nodeHits;
    std::map<std::pair<std::string, std::string>, int> edges;

    static std::string escape(const std::string& text) {
        std::string result;
        for (char ch : text) {
            if (ch == '"') result += "\\\"";
            else if (ch == '\\') result += "\\\\";
            else result += ch;
        }
        return result;
    }
};

CallGraph graph;

NOINLINE void sampleCallStack() {
    graph.addStack(captureStack());
}

NOINLINE void saveReport() {
    sampleCallStack();
}

NOINLINE void buildTable() {
    sampleCallStack();
    saveReport();
}

NOINLINE void calculateStatistics() {
    sampleCallStack();
    buildTable();
}

NOINLINE void validateInput() {
    sampleCallStack();
}

NOINLINE void processRequest() {
    validateInput();
    calculateStatistics();
    sampleCallStack();
}

NOINLINE void handleClient() {
    sampleCallStack();
    processRequest();
}

NOINLINE void applicationLoop(int count) {
    for (int i = 0; i < count; ++i) {
        handleClient();
    }
}

int main() {
    applicationLoop(3);

    graph.writeDot("callgraph.dot");

    std::cout << "Call graph saved to callgraph.dot\n";
    std::cout << "Render example: dot -Tpng callgraph.dot -o callgraph.png\n";

    return 0;
}