Compare commits
16 Commits
dcee7c054f
...
6ba13bbeda
Author | SHA1 | Date |
---|---|---|
|
6ba13bbeda | |
|
8e31c88671 | |
|
24a26d8683 | |
|
6b98718848 | |
|
7e29400b52 | |
|
a62653dba9 | |
|
e9f3e9a236 | |
|
a66ea40850 | |
|
7112c1cccf | |
|
a72ba8066c | |
|
cad600fd59 | |
|
f284304dd6 | |
|
b9978801a3 | |
|
12ee7a6ad7 | |
|
659a872024 | |
|
a0b3af9c40 |
|
@ -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)
|
4
rvprof.h
4
rvprof.h
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue