Загрузка данных
// 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;
}