Compare commits

...

16 Commits

Author SHA1 Message Date
Patrick Lipka 6ba13bbeda Add makefile 2025-08-18 17:41:27 +02:00
Patrick Lipka 8e31c88671 add missing context management 2025-08-18 17:41:20 +02:00
Patrick Lipka 24a26d8683 add main function detection 2025-08-18 17:32:24 +02:00
Patrick Lipka 6b98718848 generate output filename 2025-08-18 17:31:03 +02:00
Patrick Lipka 7e29400b52 add program name resolution 2025-08-18 17:30:05 +02:00
Patrick Lipka a62653dba9 fix cycles_available 2025-08-18 17:26:46 +02:00
Patrick Lipka e9f3e9a236 add timing logic 2025-08-18 17:25:33 +02:00
Patrick Lipka a66ea40850 add signal handler 2025-08-18 17:16:16 +02:00
Patrick Lipka 7112c1cccf add public API 2025-08-18 17:09:59 +02:00
Patrick Lipka a72ba8066c bugfixes 2025-08-18 17:01:41 +02:00
Patrick Lipka cad600fd59 Add cygnus hooks declarations 2025-08-18 17:00:48 +02:00
Patrick Lipka f284304dd6 add symbol lookup 2025-08-18 16:48:33 +02:00
Patrick Lipka b9978801a3 add symbol parsinng (64 and 32 bit) 2025-08-18 16:38:17 +02:00
Patrick Lipka 12ee7a6ad7 adding of interesting symbols only 2025-08-18 15:41:50 +02:00
Patrick Lipka 659a872024 Add base address calculation 2025-08-18 14:13:01 +02:00
Patrick Lipka a0b3af9c40 add stats management 2025-08-08 17:42:54 +02:00
8 changed files with 733 additions and 0 deletions

62
Makefile Normal file
View File

@ -0,0 +1,62 @@
CC = clang
FC = flang
AR = ar
# Architecture settings for RISC-V (adjust as needed)
ARCH_FLAGS =
DEBUG_FLAGS = -g
# Library settings
LIB_NAME = librvprof
STATIC_LIB = $(LIB_NAME).a
# Source directories
SRC_DIR = src
INCLUDE_DIR = include
# Source files
C_SOURCES = \
$(SRC_DIR)/rvprof_core.c \
$(SRC_DIR)/rvprof_timing.c \
$(SRC_DIR)/rvprof_symbols.c \
$(SRC_DIR)/rvprof_memory.c \
$(SRC_DIR)/rvprof_hooks.c \
$(SRC_DIR)/rvprof_output.c \
$(SRC_DIR)/rvprof_utils.c \
$(SRC_DIR)/rvprof_stats.c \
$(SRC_DIR)/rvprof_context.c \
$(SRC_DIR)/rvprof_fortran.c
F_SOURCES = rvprof_module.f90
HEADERS = rvprof.h $(SRC_DIR)/rvprof_internal.h
# Object files
C_OBJECTS = $(C_SOURCES:.c=.o)
F_OBJECTS = $(F_SOURCES:.f90=.o)
ALL_OBJECTS = $(C_OBJECTS) $(F_OBJECTS)
# Module files (generated by Fortran compiler)
MOD_FILES = rvprof.mod
# Default target
all: $(STATIC_LIB)
# Static library
$(STATIC_LIB): $(ALL_OBJECTS)
@echo "Creating static library $@"
$(AR) rcs $@ $^
# C object files
$(SRC_DIR)/%.o: $(SRC_DIR)/%.c $(HEADERS)
@echo "Compiling C source $<"
$(CC) $(CFLAGS) $(DEBUG_FLAGS) -I. -c $< -o $@
# Fortran object files (depends on C objects for linking)
%.o: %.f90 $(C_OBJECTS)
@echo "Compiling Fortran module $<"
$(FC) $(FFLAGS) -c $< -o $@
clean:
rm -f $(ALL_OBJECTS) $(MOD_FILES)
rm -f $(STATIC_LIB)

View File

@ -21,6 +21,10 @@ void rvprof_region_end(const char* name);
// not called by instrumented code direclty anymore
void rvprof_finalize(void);
// cygnus function hooks
void __cyg_profile_func_enter(void *this_fn, void *call_site);
void __cyg_profile_func_exit(void *this_fn, void *call_site);
#ifdef __cplusplus
extern }
#endif

41
src/rvprof_context.c Normal file
View File

@ -0,0 +1,41 @@
#include "rvprof_internal.h"
void rvprof_context_cleanup(void){
if (!g_rvprof.initialized) {
return;
}
// generate report
rvprof_output_generate_report();
// close output file
if (g_rvprof.output_file) {
fclose(g_rvprof.output_file);
g_rvprof.output_file = NULL;
}
// clean up dynamic arrays
region_array_cleanup(&g_rvprof.regions);
function_stats_array_cleanup(&g_rvprof.functions);
stack_info_array_cleanup(&g_rvprof.stacks);
symbol_array_cleanup(&g_rvprof.symbols);
// clean up configuration strings
if (g_rvprof.config.output_filename) {
rvprof_free(g_rvprof.config.output_filename,
strlen(g_rvprof.config.output_filename) + 1);
g_rvprof.config.output_filename = NULL;
}
if (g_rvprof.config.program_name) {
rvprof_free(g_rvprof.config.program_name,
strlen(g_rvprof.config.program_name) + 1);
g_rvprof.config.program_name = NULL;
}
// reset state
g_rvprof.initialized = 0;
g_rvprof.auto_initialized = 0;
fprintf(stderr, "RVProf: Finalized\n");
}

View File

@ -185,6 +185,8 @@ int rvprof_stats_get_or_create_stack_id(void);
void rvprof_stats_add_stack_id_to_function(int func_id, int stack_id);
void rvprof_stats_add_caller_to_function(int func_id, const char* caller);
// context management
void rvprof_context_cleanup(void);
#ifdef __cplusplus
}

73
src/rvprof_stats.c Normal file
View File

@ -0,0 +1,73 @@
#include "rvprof_internal.h"
// get function id for given name / caller combination or create new one
int rvprof_stats_find_or_create_function( const char* name, const char* caller){
if(g_rvprof.config.merge_regions){
// ignore caller when merging stacks
for (int i=0; i<g_rvprof.functions.size; i++){
if (strcmp(g_rvprof.functions.data[i].name, name) == 0){
return i;
}
}
} else {
for (int i = 0; i<g_rvprof.functions.size; i++) {
if (strcmp(g_rvprof.functions.data[i].name, name) == 0 && strcmp(g_rvprof.functions.data[i].caller, caller) == 0) {
return i;
}
}
}
// ensure capacity
int err = function_stats_array_ensure_capacity(&g_rvprof.functions, g_rvprof.functions.size);
if (err < 0) return -1;
// add entry
int idx = g_rvprof.functions.size++;
function_stats_t* stats = &g_rvprof.functions.data[idx];
memset(stats, 0, sizeof(function_stats_t));
strncpy(stats->name, name, MAX_NAME_LEN-1);
stats->name[MAX_NAME_LEN-1] = '\0';
if(!g_rvprof.config.merge_regions){
strncpy(stats->caller, caller, MAX_NAME_LEN-1);
stats->caller[MAX_NAME_LEN-1] = '\0';
}
return idx;
}
// get call stack or create it
int rvprof_stats_get_or_create_stack_id(void) {
static char call_stack[4096];
call_stack [0] = '\0';
for (int i=0; i<=g_rvprof.stack_ptr; i++){
if (i>0){
strcat(call_stack, "<");
}
strcat(call_stack, g_rvprof.regions.data[i].name);
}
// check if this stack exists already
for (int i=0; i<g_rvprof.stacks.size; i++){
if (g_rvprof.stacks.data[i].stack_path && strcmp(g_rvprof.stacks.data[i].stack_path, call_stack) == 0){
return g_rvprof.stacks.data[i].stid;
}
}
// create new stack
int err = stack_info_array_ensure_capacity(&g_rvprof.stacks, g_rvprof.stacks.size);
if (err < 0) return -1;
int stack_id = g_rvprof.stacks.size + 1; // start at 1
// add stack
g_rvprof.stacks.data[g_rvprof.stacks.size].stack_path = rvprof_malloc(strlen(call_stack)+1);
if (!g_rvprof.stacks.data[g_rvprof.stacks.size].stack_path){
return -1;
}
strcpy(g_rvprof.stacks.data[g_rvprof.stacks.size].stack_path, call_stack);
g_rvprof.stacks.data[g_rvprof.stacks.size].stid = stack_id;
g_rvprof.stacks.size++;
return stack_id;
}

363
src/rvprof_symbols.c Normal file
View File

@ -0,0 +1,363 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <elf.h>
#include <ctype.h>
#include "rvprof_internal.h"
// thread-local storage for function names
__thread char g_tmp_func_name[128];
// base offset for PIE
static uintptr_t g_base_address_offset = 0;
static int g_base_address_calculated = 0;
// base address calculatiion
static void calculate_base_address_offset(void* known_runtime_addr, uintptr_t known_elf_addr){
if (g_base_address_calculated) return;
uintptr_t runtime_addr = (uintptr_t)known_runtime_addr;
if (runtime_addr > known_elf_addr){
g_base_address_offset = runtime_addr - known_elf_addr;
}
g_base_address_calculated = 1;
}
// compare function for symbol table quicksort
static int compare_symbol(const void* a, const void* b){
const symbol_entry_t* sym_a = (const symbol_entry_t*)a;
const symbol_entry_t* sym_b = (const symbol_entry_t*)b;
if (sym_a->addr < sym_b->addr) return -1;
if (sym_a->addr > sym_b->addr) return 1;
return 0;
}
// add symbol to symbol table
static int add_symbol(uintptr_t addr, const char* name, size_t size){
if (!name || strlen(name) == 0) return 0;
// skip most internal symbols
if (name[0] == '_' && name[1] == '_'){
if (strncmp(name, "__cyg_profile", 13) == 0) return 0;
if (strncmp(name, "__libc_", 7) == 0) return 0;
if (strncmp(name, "__gmon_", 7) == 0) return 0;
if (strstr(name, "_start") == NULL && strstr(name, "_init") == NULL && strstr(name, "_fini") == NULL && strstr(name, "main") == NULL) {
return 0;
}
}
// skip section symbols
if (strstr(name, ".") != NULL) return 0;
// skip versioned symbols
if (strstr(name, "@") != NULL) return 0;
// add interesting symbols
size_t name_len = strlen(name);
char* tmp_name = rvprof_malloc(name_len+1);
if (!tmp_name) return -1;
strcpy(tmp_name, name);
g_rvprof.symbols.data[g_rvprof.symbols.size].addr = addr;
g_rvprof.symbols.data[g_rvprof.symbols.size].name = tmp_name;
g_rvprof.symbols.data[g_rvprof.symbols.size].size = size;
g_rvprof.symbols.size++;
return 0;
}
// ELF symbol parsing
static int parse_elf_symbols(const char* filepath){
int fp = open(filepath, O_RDONLY);
if (fp< 0) return -1;
struct stat st;
if (fstat(fp, &st) < 0){
close(fp);
return -1;
}
// sanity check: file size, max fort executables defined here: 1 GiB
if (st.st_size < (off_t)sizeof(Elf64_Ehdr) || st.st_size > 1024*1024*1024){
close(fp);
return -1;
}
void* mapped = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fp, 0);
close(fp);
// check ELF magic with bounds checkuing
if (st.st_size < 16){
munmap(mapped, st.st_size);
return -1;
}
unsigned char* elf_data = (unsigned char*)mapped;
if (elf_data[0] != 0x7f || elf_data[1] != 'E' || elf_data[2] != 'L' || elf_data[3] != 'F'){
// not an ELF file
munmap(mapped, st.st_size);
return -1;
}
// 32 or 64 bit ELF ?
int is_64bit = (elf_data[4] == 2);
if(is_64bit){
if(st.st_size < (off_t)sizeof(Elf64_Ehdr)){
// mismatch
munmap(mapped, st.st_size);
return -1;
}
Elf64_Ehdr* ehdr = (Elf64_Ehdr*)mapped;
// bounds checking: section header table
if (ehdr->e_shoff == 0 || ehdr->e_shnum == 0 || ehdr->e_shoff + (ehdr->e_shnum * sizeof(Elf64_Shdr)) > (size_t)st.st_size){
munmap(mapped, st.st_size);
return -1;
}
Elf64_Shdr* shdrs = (Elf64_Shdr*)((char*)mapped + ehdr->e_shoff);
// bounds checking: string table index
if (ehdr->e_shstrndx >= ehdr->e_shnum){
munmap(mapped, st.st_size);
return -1;
}
// parse .symtab and .dynsym sections
for (int i=0; i<ehdr->e_shnum; i++){
Elf64_Shdr *shdr = &shdrs[i];
if (shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM){
// bounds checking: symbol table
if (shdr->sh_offset + shdr->sh_size > (size_t)st.st_size) continue;
if (shdr->sh_size == 0 || shdr->sh_size % sizeof(Elf64_Sym) != 0) continue;
// find associated string table
if (shdr->sh_link >= ehdr->e_shnum) continue;
Elf64_Shdr* strtab_shdr = &shdrs[shdr->sh_link];
// bounds checking: string table
if (strtab_shdr->sh_offset + strtab_shdr->sh_size > (size_t)st.st_size) continue;
if (strtab_shdr->sh_size == 0) continue;
char* strtab = (char*)mapped + strtab_shdr->sh_offset;
Elf64_Sym* symbols = (Elf64_Sym*)((char*)mapped + shdr->sh_offset);
int num_syms = shdr->sh_size / sizeof(Elf64_Sym);
for (int j=0; j<num_syms; j++){
Elf64_Sym* sym = &symbols[j];
// only process function symbols
if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) continue;
if (sym->st_value == 0) continue;
if (sym->st_name == 0) continue;
if (sym->st_name >= strtab_shdr->sh_size) continue;
char* name = &strtab[sym->st_name];
// verify null termination within bounds
int name_len = strnlen(name, strtab_shdr->sh_size - sym->st_name);
if (name_len == 0 || name_len >= (int)(strtab_shdr->sh_size - sym->st_name)) continue;
add_symbol(sym->st_value, name, sym->st_size);
}
}
}
} else{
// 32-bit case (similar logic)
if (st.st_size < (off_t)sizeof(Elf32_Ehdr)){
munmap(mapped, st.st_size);
return -1;
}
Elf32_Ehdr* ehdr = (Elf32_Ehdr*)mapped;
if (ehdr->e_shoff == 0 || ehdr->e_shnum == 0 ||
ehdr->e_shoff + (ehdr->e_shnum * sizeof(Elf32_Shdr)) > (size_t)st.st_size){
munmap(mapped, st.st_size);
return -1;
}
Elf32_Shdr* shdrs = (Elf32_Shdr*)((char*)mapped + ehdr->e_shoff);
if (ehdr->e_shstrndx >= ehdr->e_shnum){
munmap(mapped, st.st_size);
return -1;
}
for (int i = 0; i < ehdr->e_shnum; i++){
Elf32_Shdr* shdr = &shdrs[i];
if (shdr->sh_type == SHT_SYMTAB || shdr->sh_type == SHT_DYNSYM){
if (shdr->sh_offset + shdr->sh_size > (size_t)st.st_size) continue;
if (shdr->sh_size == 0 || shdr->sh_size % sizeof(Elf32_Sym) != 0) continue;
if (shdr->sh_link >= ehdr->e_shnum) continue;
Elf32_Shdr* strtab_shdr = &shdrs[shdr->sh_link];
if (strtab_shdr->sh_offset + strtab_shdr->sh_size > (size_t)st.st_size) continue;
if (strtab_shdr->sh_size == 0) continue;
char* strtab = (char*)mapped + strtab_shdr->sh_offset;
Elf32_Sym* symbols = (Elf32_Sym*)((char*)mapped + shdr->sh_offset);
int num_syms = shdr->sh_size / sizeof(Elf32_Sym);
for (int j = 0; j < num_syms; j++){
Elf32_Sym* sym = &symbols[j];
if (ELF32_ST_TYPE(sym->st_info) != STT_FUNC) continue;
if (sym->st_value == 0) continue;
if (sym->st_name == 0) continue;
if (sym->st_name >= strtab_shdr->sh_size) continue;
char* name = &strtab[sym->st_name];
int name_len = strnlen(name, strtab_shdr->sh_size - sym->st_name);
if (name_len == 0 || name_len >= (int)(strtab_shdr->sh_size - sym->st_name)) continue;
add_symbol(sym->st_value, name, sym->st_size);
}
}
}
}
munmap(mapped, st.st_size);
// sort symbols by address for quick binary search
if (g_rvprof.symbols.size > 0){
qsort(g_rvprof.symbols.data, g_rvprof.symbols.size, sizeof(symbol_entry_t), compare_symbol);
g_rvprof.symbols_loaded = 1;
}
return 0;
}
// symbol lookup
const char* rvprof_symbols_lookup(void* addr){
if (!g_rvprof.symbols_loaded || g_rvprof.symbols.size == 0){
snprintf(g_tmp_func_name, sizeof(g_tmp_func_name), "func_%p", addr);
return g_tmp_func_name;
}
uintptr_t target_addr = (uintptr_t)addr;
// try to calculate base address offset using known symbols
if (!g_base_address_calculated && g_rvprof.symbols.size > 0){
// use our own profiling functions as reference points since we know their addresses
for (int i = 0; i < g_rvprof.symbols.size; i++){
symbol_entry_t* sym = &g_rvprof.symbols.data[i];
// use rvprof functions as reference points
if (strcmp(sym->name, "__cyg_profile_func_enter") == 0){
calculate_base_address_offset((void*)__cyg_profile_func_enter, sym->addr);
break;
} else if (strcmp(sym->name, "rvprof_init") == 0){
calculate_base_address_offset((void*)rvprof_init, sym->addr);
break;
}
}
// if we still haven't calculated it, try to infer from the lookup addresses
if (!g_base_address_calculated && g_rvprof.symbols.size > 0){
// find the closest symbol and use it to estimate base offset
uintptr_t highest_elf_addr = 0;
for (int i = 0; i < g_rvprof.symbols.size; i++) {
if (g_rvprof.symbols.data[i].addr > highest_elf_addr) {
highest_elf_addr = g_rvprof.symbols.data[i].addr;
}
}
// if the target address is much higher than any ELF address, calculate offset
if (target_addr > highest_elf_addr && (target_addr - highest_elf_addr) > 0x100000){
// estimate base offset
uintptr_t estimated_offset = target_addr - highest_elf_addr;
// round down to page boundary (typically 4KB)
g_base_address_offset = (estimated_offset & ~0xFFF);
g_base_address_calculated = 1;
}
}
}
// adjust target address by base offset
uintptr_t adjusted_addr = target_addr;
if (g_base_address_calculated && g_base_address_offset > 0){
adjusted_addr = target_addr - g_base_address_offset;
}
// binary search for closest symbol
int left = 0;
int right = g_rvprof.symbols.size - 1;
int best_match = -1;
while (left <= right){
int mid = (left + right) / 2;
if (g_rvprof.symbols.data[mid].addr <= adjusted_addr){
best_match = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
// match?
if (best_match >= 0) {
symbol_entry_t* sym = &g_rvprof.symbols.data[best_match];
// if symbol has size info, check if address is within range
if (sym->size > 0) {
if (adjusted_addr >= sym->addr && adjusted_addr < sym->addr + sym->size){
strncpy(g_tmp_func_name, sym->name, sizeof(g_tmp_func_name) - 1);
g_tmp_func_name[sizeof(g_tmp_func_name) - 1] = '\0';
return g_tmp_func_name;
}
} else {
// no size info, use 64 KB heuristic: must be within reasonable range
if (adjusted_addr >= sym->addr && (adjusted_addr - sym->addr) < 0x10000){
strncpy(g_tmp_func_name, sym->name, sizeof(g_tmp_func_name) - 1);
g_tmp_func_name[sizeof(g_tmp_func_name) - 1] = '\0';
return g_tmp_func_name;
}
}
}
// fallback to addresses if we cannot resolve symbols
snprintf(g_tmp_func_name, sizeof(g_tmp_func_name), "func_%p", addr);
return g_tmp_func_name;
}
// public API
rvprof_error_t rvprof_symbols_init(const char* program_path){
// option 1: provided path
if (program_path && parse_elf_symbols(program_path) == 0){
return RVPROF_SUCCESS;
}
// option 2: try /proc/self/exe (Linux only)
if (parse_elf_symbols("/proc/self/exe") == 0){
return RVPROF_SUCCESS;
}
// option 3: try typical paths
const char* fallbacks[] = {
"a.out",
"./a.out",
NULL
};
for (int i=0; fallbacks[i]; i++){
if (parse_elf_symbols(fallbacks[i]) == 0){
return RVPROF_SUCCESS;
}
}
// symbol loading failed, fall back to addresses
return RVPROF_ERROR_SYMBOLS;
}
void rvprof_symbols_cleanup(void){
symbol_array_cleanup(&g_rvprof.symbols);
g_rvprof.symbols_loaded = 0;
}

100
src/rvprof_timing.c Normal file
View File

@ -0,0 +1,100 @@
#include <time.h>
#include <signal.h>
#include <setjmp.h>
#include "rvprof_internal.h"
/*
The inline assembly calls in this file are the only places where the code is really RISCV-specific.
Maybe replace with a library call in the future
*/
// global flag for cycle counter availability
static volatile int g_cycle_counter_available = 0;
static volatile jmp_buf g_cycle_test_jmpbuf;
// signal handler for illegal instructions
static void sigkill_handler(int sig){
g_cycle_counter_available = 0;
longjmp(g_cycle_test_jmpbuf, 1);
}
static inline uint64_t read_cycles(void){
if (!g_cycle_counter_available) {
return 0;
}
uint64_t cycles;
__asm__ volatile("csrr %0, cycle" : "=r"(cycles));
return cycles;
}
// nanosecond timing for all actual measurements
static inline uint64_t read_time_ns(void){
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
}
int rvprof_timing_test_cycle_counter(void){
// set up signal handler for illegal instruction
struct sigaction old_action, new_action;
new_action.sa_handler = sigkill_handler;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
if (sigaction(SIGILL, &new_action, &old_action) != 0){
// can't set up signal handler, assume cycle counter not available
g_cycle_counter_available = 0;
return 0;
}
// test cycle counter access
if (setjmp(g_cycle_test_jmpbuf) == 0) {
uint64_t cycles1, cycles2;
__asm__ volatile("csrr %0, cycle" : "=r"(cycles1));
// small delay
for (volatile int i = 0; i < 1000; i++);
__asm__ volatile("csrr %0, cycle" : "=r"(cycles2));
if (cycles2 > cycles1){
g_cycle_counter_available = 1;
} else {
g_cycle_counter_available = 0;
}
}else{
// illegal instruction occurred
g_cycle_counter_available = 0;
}
// restore original signal handler
sigaction(SIGILL, &old_action, NULL);
return g_cycle_counter_available;
}
rvprof_error_t rvprof_timing_init(void){
// always use nanosecond timer for actual timing
g_rvprof.use_cycles = 0;
// test if cycle counter is available for informational display
g_rvprof.cycles_avaiable = rvprof_timing_test_cycle_counter();
return RVPROF_SUCCESS;
}
uint64_t rvprof_timing_get_current(void) {
return read_time_ns();
}
uint64_t rvprof_timing_get_cycles(void) {
return read_cycles(); // Returns 0 if not available
}
double rvprof_timing_to_seconds(uint64_t nanoseconds) {
return (double)nanoseconds / 1000000000.0;
}

88
src/rvprof_utils.c Normal file
View File

@ -0,0 +1,88 @@
#include "rvprof_internal.h"
char* rvprof_utils_get_program_name(void){
static char resolved_name[256];
char link_path[32];
ssize_t len;
// option 1: try /proc/self/exe (Linux only)
snprintf(link_path, sizeof(link_path), "/proc/self/exe");
len = readlink(link_path, resolved_name, sizeof(resolved_name) - 1);
if (len > 0) {
resolved_name[len] = '\0';
// Extract just the filename from full path
char* basename = strrchr(resolved_name, '/');
if (basename) {
return basename + 1; // Skip the '/'
}
return resolved_name;
}
// option 2: try /proc/self/cmdline (fallback)
FILE* cmdline = fopen("/proc/self/cmdline", "r");
if (cmdline) {
if (fgets(resolved_name, sizeof(resolved_name), cmdline)) {
fclose(cmdline);
char* basename = strrchr(resolved_name, '/');
if (basename) {
return basename + 1;
}
return resolved_name;
}
fclose(cmdline);
}
// option 3: use provided program name if available
if (g_rvprof.config.program_name) {
char* basename = strrchr(g_rvprof.config.program_name, '/');
if (basename) {
return basename + 1;
}
return g_rvprof.config.program_name;
}
// option 4: fallback
return "unknown_program";
}
char* rvprof_utils_generate_output_filename(void){
if (g_rvprof.config.output_filename) {
return g_rvprof.config.output_filename;
}
const char* prog_name = rvprof_utils_get_program_name();
size_t name_len = strlen(prog_name);
const char* suffix = "_rvprof.log";
size_t suffix_len = strlen(suffix);
char* filename = rvprof_malloc(name_len + suffix_len + 1);
if (filename) {
strcpy(filename, prog_name);
strcat(filename, suffix);
g_rvprof.config.output_filename = filename;
return filename;
}
// fallback
return "rvprof_output.log";
}
// main function detection
int rvprof_utils_is_main_function(const char* func_name) {
if (!func_name) return 0;
// C main function
if (strcmp(func_name, "main") == 0) return 1;
// variety of possible names for Fortran
if (strstr(func_name, "MAIN__") != NULL) return 1;
if (strstr(func_name, "main_program") != NULL) return 1;
if (strstr(func_name, "_main_program") != NULL) return 1;
if (strstr(func_name, "_QQmain") != NULL) return 1;
if (strncmp(func_name, "MAIN_", 5) == 0) return 1;
if (strncmp(func_name, "_MAIN_", 6) == 0) return 1;
if (strstr(func_name, "__main_program_MOD_") != NULL) return 1;
if (strcmp(func_name, "_gfortran_main") == 0) return 1;
return 0;
}