Compare commits
No commits in common. "6ba13bbeda548fc82140812ae059901e31f3840b" and "dcee7c054f1a6ee71494ac3daa2828fe1cf7b9ec" have entirely different histories.
6ba13bbeda
...
dcee7c054f
62
Makefile
62
Makefile
|
@ -1,62 +0,0 @@
|
||||||
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,10 +21,6 @@ void rvprof_region_end(const char* name);
|
||||||
// not called by instrumented code direclty anymore
|
// not called by instrumented code direclty anymore
|
||||||
void rvprof_finalize(void);
|
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
|
#ifdef __cplusplus
|
||||||
extern }
|
extern }
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
#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,8 +185,6 @@ 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_stack_id_to_function(int func_id, int stack_id);
|
||||||
void rvprof_stats_add_caller_to_function(int func_id, const char* caller);
|
void rvprof_stats_add_caller_to_function(int func_id, const char* caller);
|
||||||
|
|
||||||
// context management
|
|
||||||
void rvprof_context_cleanup(void);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,363 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
#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