mirror of
https://gitlab.com/skmp/dca3-game.git
synced 2025-01-17 05:18:21 +01:00
570 lines
22 KiB
C++
570 lines
22 KiB
C++
#include <iostream>
|
|
#include <fstream>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <regex>
|
|
#include <set>
|
|
#include <unordered_set>
|
|
#include <functional>
|
|
#include <cxxabi.h> // For demangling
|
|
|
|
#define SAMPLE_PERCENTAGE_THRESHOLD 0.1 // Threshold for including functions
|
|
|
|
struct GmonHeader {
|
|
char cookie[4]; // 'g','m','o','n'
|
|
int32_t version; // 1
|
|
char spare[12]; // Padding
|
|
};
|
|
|
|
struct GmonArc {
|
|
uint32_t from_pc; // Address within caller's body
|
|
uint32_t self_pc; // Address within callee's body
|
|
uint32_t count; // Number of arc traversals
|
|
};
|
|
|
|
struct FunctionInfo {
|
|
std::string mangled_name; // Mangled function name
|
|
std::string demangled_name; // Demangled function name
|
|
uint32_t start_address;
|
|
uint32_t end_address; // Will be updated as we parse the source file
|
|
uint64_t sample_count;
|
|
double sample_percentage; // Percentage of total samples
|
|
uint64_t samples_with_children; // Total samples including callees
|
|
std::unordered_map<uint32_t, uint64_t> callers; // Set of caller addresses
|
|
};
|
|
|
|
// Updated demangle_symbol function using abi::__cxa_demangle
|
|
std::string demangle_symbol(const std::string& mangled_name) {
|
|
static std::unordered_map<std::string, std::string> demangle_cache;
|
|
|
|
// Check if the name is already in the cache
|
|
auto cache_it = demangle_cache.find(mangled_name);
|
|
if (cache_it != demangle_cache.end()) {
|
|
return cache_it->second;
|
|
}
|
|
|
|
int status = 0;
|
|
char* demangled = abi::__cxa_demangle(mangled_name.c_str(), nullptr, nullptr, &status);
|
|
std::string demangled_name;
|
|
if (status == 0 && demangled != nullptr) {
|
|
demangled_name = demangled;
|
|
free(demangled);
|
|
} else {
|
|
// If demangling fails, return the mangled name
|
|
demangled_name = mangled_name;
|
|
}
|
|
demangle_cache[mangled_name] = demangled_name;
|
|
return demangled_name;
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
// Enable synchronization between C++ and C I/O
|
|
std::ios::sync_with_stdio(false);
|
|
|
|
// Check for the required arguments
|
|
if (argc < 3) {
|
|
std::cerr << "Usage: " << argv[0] << " <gmon_file> <source_dump_file>" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
const char* gmon_filename = argv[1]; // Profiler output file
|
|
const char* source_filename = argv[2]; // Source dump file
|
|
|
|
std::cout << "[*] Starting profiling data analysis..." << std::endl;
|
|
|
|
// Open the profiler output file
|
|
std::cout << "[*] Reading profiler output file: " << gmon_filename << std::endl;
|
|
std::ifstream infile(gmon_filename, std::ios::binary);
|
|
if (!infile) {
|
|
std::cerr << "Error opening file " << gmon_filename << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Read GmonHeader
|
|
GmonHeader header;
|
|
infile.read(reinterpret_cast<char*>(&header), sizeof(GmonHeader));
|
|
|
|
// Verify the header
|
|
if (std::strncmp(header.cookie, "gmon", 4) != 0) {
|
|
std::cerr << "Invalid file format: missing 'gmon' cookie" << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Collect arc records
|
|
std::vector<GmonArc> arcs;
|
|
while (infile.peek() != EOF) {
|
|
uint8_t tag;
|
|
infile.read(reinterpret_cast<char*>(&tag), sizeof(tag));
|
|
|
|
if (infile.eof()) {
|
|
break;
|
|
}
|
|
|
|
if (tag == 1) {
|
|
// Arc record
|
|
GmonArc arc;
|
|
infile.read(reinterpret_cast<char*>(&arc), sizeof(GmonArc));
|
|
arcs.push_back(arc);
|
|
} else if (tag == 0) {
|
|
// Histogram data: skip it
|
|
// Read GmonHistHeader to get hist_size
|
|
struct {
|
|
uint32_t low_pc;
|
|
uint32_t high_pc;
|
|
uint32_t hist_size;
|
|
uint32_t prof_rate;
|
|
char dimen[15];
|
|
char dimen_abbrev;
|
|
} hist_header;
|
|
|
|
infile.read(reinterpret_cast<char*>(&hist_header), sizeof(hist_header));
|
|
|
|
// Skip the histogram bins
|
|
infile.ignore(hist_header.hist_size * sizeof(uint16_t));
|
|
} else {
|
|
std::cerr << "Unknown tag encountered: " << static_cast<int>(tag) << std::endl;
|
|
break;
|
|
}
|
|
}
|
|
infile.close();
|
|
std::cout << "[*] Profiler data reading completed." << std::endl;
|
|
|
|
// Create a mapping from self_pc to count
|
|
std::unordered_map<uint32_t, uint64_t> address_counts;
|
|
uint64_t total_samples = 0;
|
|
for (const auto& arc : arcs) {
|
|
// Sum counts for each address
|
|
address_counts[arc.self_pc] += arc.count;
|
|
total_samples += arc.count;
|
|
}
|
|
std::cout << "[*] Total samples collected: " << total_samples << std::endl;
|
|
|
|
// Open the source dump file
|
|
std::cout << "[*] Processing source dump file: " << source_filename << std::endl;
|
|
std::ifstream source_file(source_filename);
|
|
if (!source_file) {
|
|
std::cerr << "Error opening source dump file " << source_filename << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// Prepare the output annotated file
|
|
std::string output_filename = std::string(source_filename) + ".annotated";
|
|
std::ofstream annotated_file(output_filename);
|
|
if (!annotated_file) {
|
|
std::cerr << "Error creating annotated file " << output_filename << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
// First pass: Identify functions and their ranges
|
|
std::cout << "[*] Identifying functions and their address ranges..." << std::endl;
|
|
std::string line;
|
|
FunctionInfo current_function;
|
|
current_function.start_address = 0;
|
|
current_function.end_address = 0;
|
|
current_function.sample_count = 0;
|
|
|
|
std::vector<FunctionInfo> functions;
|
|
uint32_t last_address = 0;
|
|
while (std::getline(source_file, line)) {
|
|
// Regular expression to match function headers
|
|
// Pattern: address <function_name>:
|
|
std::regex function_header_regex(R"(^\s*([0-9a-fA-F]+)\s+<_(.*)>:)");
|
|
|
|
std::smatch match;
|
|
if (std::regex_search(line, match, function_header_regex)) {
|
|
uint32_t address = 0;
|
|
std::istringstream addr_ss(match[1]);
|
|
addr_ss >> std::hex >> address;
|
|
|
|
std::string function_name = match[2];
|
|
|
|
// Use address as name if function_name is empty
|
|
if (function_name.empty()) {
|
|
function_name = "0x" + match[1].str();
|
|
}
|
|
|
|
// Finish the previous function
|
|
if (current_function.start_address != 0) {
|
|
current_function.end_address = last_address + 1;
|
|
functions.push_back(current_function);
|
|
}
|
|
|
|
// Start a new function
|
|
current_function.mangled_name = function_name;
|
|
current_function.demangled_name = demangle_symbol(function_name);
|
|
current_function.start_address = address;
|
|
current_function.end_address = 0;
|
|
current_function.sample_count = 0;
|
|
|
|
last_address = address;
|
|
continue;
|
|
}
|
|
|
|
// Attempt to parse an address at the beginning of the line
|
|
std::istringstream iss(line);
|
|
std::string address_str;
|
|
iss >> address_str;
|
|
|
|
// Remove any trailing colon from the address
|
|
if (!address_str.empty() && address_str.back() == ':') {
|
|
address_str.pop_back();
|
|
}
|
|
|
|
uint32_t address = 0;
|
|
std::istringstream addr_ss(address_str);
|
|
addr_ss >> std::hex >> address;
|
|
|
|
if (!addr_ss.fail()) {
|
|
last_address = address;
|
|
}
|
|
}
|
|
|
|
// Finish the last function
|
|
if (current_function.start_address != 0) {
|
|
current_function.end_address = UINT32_MAX; // Assume it goes to the end
|
|
functions.push_back(current_function);
|
|
}
|
|
std::cout << "[*] Function identification completed. Total functions found: " << functions.size() << std::endl;
|
|
|
|
source_file.clear();
|
|
source_file.seekg(0, std::ios::beg); // Reset the file stream
|
|
|
|
// Map addresses to functions
|
|
// For efficiency, we can sort the functions and use binary search
|
|
std::sort(functions.begin(), functions.end(), [](const FunctionInfo& a, const FunctionInfo& b) {
|
|
return a.start_address < b.start_address;
|
|
});
|
|
|
|
// Second pass: Calculate sample counts and percentages per function
|
|
std::cout << "[*] Calculating sample counts per function..." << std::endl;
|
|
for (auto& func : functions) {
|
|
func.sample_count = 0;
|
|
func.samples_with_children = 0; // Initialize
|
|
}
|
|
|
|
// Function to find a function by address
|
|
auto find_function_by_address = [&](uint32_t address) -> FunctionInfo* {
|
|
// Use binary search since functions are sorted by start_address
|
|
auto func_it = std::upper_bound(functions.begin(), functions.end(), address, [](uint32_t addr, const FunctionInfo& func) {
|
|
return addr < func.start_address;
|
|
});
|
|
|
|
if (func_it != functions.begin()) {
|
|
--func_it;
|
|
if (address >= func_it->start_address && address < func_it->end_address) {
|
|
return &(*func_it);
|
|
}
|
|
}
|
|
return nullptr; // Function not found
|
|
};
|
|
|
|
// Calculate sample counts per function
|
|
for (const auto& addr_count : address_counts) {
|
|
uint32_t address = addr_count.first;
|
|
uint64_t count = addr_count.second;
|
|
|
|
// Find the function containing this address
|
|
FunctionInfo* func = find_function_by_address(address);
|
|
if (func) {
|
|
func->sample_count += count;
|
|
}
|
|
}
|
|
|
|
// Update caller information for each function
|
|
for (const auto& arc : arcs) {
|
|
FunctionInfo* func = find_function_by_address(arc.self_pc);
|
|
if (func) {
|
|
func->callers[arc.from_pc] += arc.count;
|
|
}
|
|
}
|
|
|
|
// Calculate sample percentages per function
|
|
for (auto& func : functions) {
|
|
func.sample_percentage = (total_samples > 0) ? (func.sample_count * 100.0) / total_samples : 0.0;
|
|
}
|
|
std::cout << "[*] Sample count calculation completed." << std::endl;
|
|
|
|
// Filter out functions with sample_percentage <= 0.1%
|
|
functions.erase(std::remove_if(functions.begin(), functions.end(), [](const FunctionInfo& func) {
|
|
return func.sample_percentage <= SAMPLE_PERCENTAGE_THRESHOLD;
|
|
}), functions.end());
|
|
|
|
// Build the call graph including only functions with > 0.1% sample_percentage
|
|
std::cout << "[*] Building the call graph including only significant functions..." << std::endl;
|
|
|
|
// Map from FunctionInfo* to callees with arc counts
|
|
std::unordered_map<FunctionInfo*, std::unordered_map<FunctionInfo*, uint64_t>> call_graph;
|
|
|
|
// Process arcs to build the call graph
|
|
for (const auto& arc : arcs) {
|
|
uint32_t from_address = arc.from_pc;
|
|
uint32_t self_address = arc.self_pc;
|
|
|
|
FunctionInfo* caller_func = find_function_by_address(from_address);
|
|
FunctionInfo* callee_func = find_function_by_address(self_address);
|
|
|
|
if (caller_func && callee_func) {
|
|
// Both functions must be significant (sample_percentage > 0.1%)
|
|
if (caller_func->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD &&
|
|
callee_func->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD) {
|
|
// Add an edge from caller to callee
|
|
call_graph[caller_func][callee_func] += arc.count;
|
|
}
|
|
}
|
|
}
|
|
std::cout << "[*] Call graph construction completed." << std::endl;
|
|
|
|
// Recompute samples_with_children for each function
|
|
std::cout << "[*] Computing samples with children for each function..." << std::endl;
|
|
std::unordered_map<FunctionInfo*, uint64_t> samples_with_children_map;
|
|
|
|
std::function<uint64_t(FunctionInfo*, std::unordered_set<FunctionInfo*>&)> compute_samples_with_children =
|
|
[&](FunctionInfo* func, std::unordered_set<FunctionInfo*>& visited) -> uint64_t {
|
|
if (samples_with_children_map.count(func)) {
|
|
return samples_with_children_map[func];
|
|
}
|
|
|
|
if (visited.count(func)) {
|
|
// Cycle detected, avoid infinite recursion
|
|
return 0;
|
|
}
|
|
visited.insert(func);
|
|
|
|
uint64_t total_samples = func->sample_count;
|
|
|
|
if (call_graph.count(func)) {
|
|
for (const auto& callee_entry : call_graph[func]) {
|
|
FunctionInfo* callee = callee_entry.first;
|
|
total_samples += compute_samples_with_children(callee, visited);
|
|
}
|
|
}
|
|
|
|
visited.erase(func);
|
|
samples_with_children_map[func] = total_samples;
|
|
return total_samples;
|
|
};
|
|
|
|
for (auto& func : functions) {
|
|
std::unordered_set<FunctionInfo*> visited;
|
|
func.samples_with_children = compute_samples_with_children(&func, visited);
|
|
}
|
|
std::cout << "[*] Samples with children computation completed." << std::endl;
|
|
|
|
// Generate the call graph in DOT format
|
|
std::cout << "[*] Generating the call graph DOT file..." << std::endl;
|
|
std::string dot_filename = std::string(source_filename) + ".graph.dot";
|
|
std::ofstream dot_file(dot_filename);
|
|
if (!dot_file) {
|
|
std::cerr << "Error creating DOT file " << dot_filename << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
dot_file << "digraph CallGraph {\n";
|
|
dot_file << " node [shape=box];\n";
|
|
|
|
// Write nodes with labels including samples_with_children%(samples_self%)
|
|
for (FunctionInfo* func_ptr = functions.data(); func_ptr != functions.data() + functions.size(); ++func_ptr) {
|
|
FunctionInfo& func = *func_ptr;
|
|
|
|
// Prioritize demangled name; if empty, use mangled name; if empty, use address
|
|
std::string function_label;
|
|
if (!func.demangled_name.empty() && func.demangled_name != func.mangled_name) {
|
|
function_label = func.demangled_name;
|
|
} else if (!func.mangled_name.empty()) {
|
|
function_label = func.mangled_name;
|
|
} else {
|
|
std::ostringstream oss;
|
|
oss << "0x" << std::hex << func.start_address;
|
|
function_label = oss.str();
|
|
}
|
|
|
|
// Label format: samples_with_children%(samples_self%)
|
|
double samples_with_children_percentage = (total_samples > 0) ? (func.samples_with_children * 100.0) / total_samples : 0.0;
|
|
double samples_self_percentage = (total_samples > 0) ? (func.sample_count * 100.0) / total_samples : 0.0;
|
|
|
|
std::ostringstream label;
|
|
label << function_label << "\\n";
|
|
label << std::fixed << std::setprecision(2) << samples_with_children_percentage << "%";
|
|
label << " (" << samples_self_percentage << "%)";
|
|
|
|
// Node identifier can be function's start_address
|
|
std::ostringstream node_id_stream;
|
|
node_id_stream << "0x" << std::hex << func.start_address;
|
|
std::string node_id = node_id_stream.str();
|
|
|
|
dot_file << " \"" << node_id << "\" [label=\"" << label.str() << "\"];\n";
|
|
}
|
|
|
|
// Write edges with arc counts
|
|
for (const auto& caller_entry : call_graph) {
|
|
FunctionInfo* caller_func = caller_entry.first;
|
|
std::ostringstream caller_id_stream;
|
|
caller_id_stream << "0x" << std::hex << caller_func->start_address;
|
|
std::string caller_id = caller_id_stream.str();
|
|
|
|
for (const auto& callee_entry : caller_entry.second) {
|
|
FunctionInfo* callee_func = callee_entry.first;
|
|
uint64_t arc_count = callee_entry.second;
|
|
|
|
std::ostringstream callee_id_stream;
|
|
callee_id_stream << "0x" << std::hex << callee_func->start_address;
|
|
std::string callee_id = callee_id_stream.str();
|
|
|
|
// Edge from caller_func to callee_func with label arc_count
|
|
dot_file << " \"" << caller_id << "\" -> \"" << callee_id << "\" [label=\"" << arc_count << "\"];\n";
|
|
}
|
|
}
|
|
|
|
dot_file << "}\n";
|
|
dot_file.close();
|
|
std::cout << "[*] Call graph DOT file generated: " << dot_filename << std::endl;
|
|
|
|
// Third pass: Annotate the source file
|
|
std::cout << "[*] Annotating the source file..." << std::endl;
|
|
source_file.clear();
|
|
source_file.seekg(0, std::ios::beg); // Reset the file stream
|
|
FunctionInfo* current_function_ptr = nullptr;
|
|
|
|
while (std::getline(source_file, line)) {
|
|
std::string original_line = line; // Keep the original line for output
|
|
|
|
// Regular expression to match function headers
|
|
std::regex function_header_regex(R"(^\s*([0-9a-fA-F]+)\s+<(.*)>:)");
|
|
|
|
std::smatch match;
|
|
if (std::regex_search(line, match, function_header_regex)) {
|
|
// Function header line
|
|
uint32_t address = 0;
|
|
std::istringstream addr_ss(match[1]);
|
|
addr_ss >> std::hex >> address;
|
|
|
|
// Find the function info
|
|
current_function_ptr = find_function_by_address(address);
|
|
|
|
// Annotate the function header line if the function is significant
|
|
if (current_function_ptr && current_function_ptr->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD) {
|
|
uint32_t func_samples = current_function_ptr->sample_count;
|
|
double func_percentage = current_function_ptr->sample_percentage;
|
|
|
|
// Prioritize demangled name; if empty, use mangled name; if empty, use address
|
|
std::string function_label;
|
|
if (!current_function_ptr->demangled_name.empty() && current_function_ptr->demangled_name != current_function_ptr->mangled_name) {
|
|
function_label = current_function_ptr->demangled_name;
|
|
} else if (!current_function_ptr->mangled_name.empty()) {
|
|
function_label = current_function_ptr->mangled_name;
|
|
} else {
|
|
std::ostringstream oss;
|
|
oss << "0x" << std::hex << current_function_ptr->start_address;
|
|
function_label = oss.str();
|
|
}
|
|
|
|
annotated_file << original_line << " !!!! " << func_samples << " " << std::fixed << std::setprecision(2) << func_percentage << "% @@@ " << function_label << std::endl;
|
|
} else {
|
|
// Function is not significant or not found
|
|
current_function_ptr = nullptr;
|
|
annotated_file << original_line << std::endl;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Attempt to parse an address at the beginning of the line
|
|
std::istringstream iss(line);
|
|
std::string address_str;
|
|
iss >> address_str;
|
|
|
|
// Remove any trailing colon from the address
|
|
if (!address_str.empty() && address_str.back() == ':') {
|
|
address_str.pop_back();
|
|
}
|
|
|
|
uint32_t address = 0;
|
|
std::istringstream addr_ss(address_str);
|
|
addr_ss >> std::hex >> address;
|
|
|
|
if (addr_ss.fail()) {
|
|
// Not a valid address, write the line as is
|
|
annotated_file << original_line << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// For code lines within significant functions
|
|
if (current_function_ptr && current_function_ptr->sample_percentage > SAMPLE_PERCENTAGE_THRESHOLD) {
|
|
// Check if the address is in the address_counts map
|
|
auto count_it = address_counts.find(address);
|
|
if (count_it != address_counts.end()) {
|
|
uint32_t sample_count = count_it->second;
|
|
uint32_t func_samples = current_function_ptr->sample_count;
|
|
double line_percentage = (func_samples > 0) ? (sample_count * 100.0) / func_samples : 0.0;
|
|
|
|
annotated_file << original_line << " !!!! " << sample_count << " " << std::fixed << std::setprecision(2) << line_percentage << "%" << std::endl;
|
|
} else {
|
|
// No samples for this line
|
|
annotated_file << original_line << std::endl;
|
|
}
|
|
} else {
|
|
// Not within a significant function
|
|
annotated_file << original_line << std::endl;
|
|
}
|
|
}
|
|
source_file.close();
|
|
annotated_file.close();
|
|
std::cout << "[*] Source file annotation completed. Annotated file: " << output_filename << std::endl;
|
|
|
|
// Generate top functions report
|
|
std::cout << "[*] Generating top functions report..." << std::endl;
|
|
|
|
// Sort functions by sample count in descending order
|
|
std::sort(functions.begin(), functions.end(), [](const FunctionInfo& a, const FunctionInfo& b) {
|
|
return a.sample_count > b.sample_count;
|
|
});
|
|
|
|
std::cout << "\nTop functions by sample count:" << std::endl;
|
|
std::cout << std::setw(6) << "Rank"
|
|
<< std::setw(10) << "Samples"
|
|
<< std::setw(10) << "Percent"
|
|
<< std::setw(10) << "CumSum"
|
|
<< " Function Name" << std::endl;
|
|
std::cout << "----------------------------------------------------------------" << std::endl;
|
|
|
|
double cumulative_percentage = 0.0;
|
|
for (size_t i = 0; i < functions.size(); ++i) {
|
|
double percent = functions[i].sample_percentage;
|
|
cumulative_percentage += percent;
|
|
|
|
// Prioritize demangled name; if empty, use mangled name; if empty, use address
|
|
std::string function_label;
|
|
if (!functions[i].demangled_name.empty() && functions[i].demangled_name != functions[i].mangled_name) {
|
|
function_label = functions[i].demangled_name;
|
|
} else if (!functions[i].mangled_name.empty()) {
|
|
function_label = functions[i].mangled_name;
|
|
} else {
|
|
std::ostringstream oss;
|
|
oss << "0x" << std::hex << functions[i].start_address;
|
|
function_label = oss.str();
|
|
}
|
|
|
|
std::cout << std::setw(6) << (i + 1)
|
|
<< std::setw(10) << functions[i].sample_count
|
|
<< std::setw(9) << std::fixed << std::setprecision(2) << percent << "%"
|
|
<< std::setw(9) << std::fixed << std::setprecision(2) << cumulative_percentage << "%"
|
|
<< " " << function_label << std::endl;
|
|
#if defined(VERBOSE_CALLERS)
|
|
for (auto &caller : functions[i].callers) {
|
|
auto func = find_function_by_address(caller.first);
|
|
std::cout << " Called by 0x" << std::hex << caller.first << " " << std::dec << caller.second << " times, " << (func ? func->demangled_name : "<unknown>") << std::endl;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::cout << "[*] Top functions report generated." << std::endl;
|
|
std::cout << "[*] Profiling data analysis completed." << std::endl;
|
|
|
|
return 0;
|
|
}
|