blob: f8a8adb7fc914fea210f75a926d6fd0835be297e [file]
/* Copyright (C) 2025-2026 Free Software Foundation, Inc.
Contributed by Oracle.
This file is part of GNU Binutils.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, 51 Franklin Street - Fifth Floor, Boston,
MA 02110-1301, USA. */
#include "config.h"
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <fstream>
#include <libgen.h>
#include <sys/mman.h>
#include "util.h"
#include "DbeApplication.h"
#include "bfd.h"
#include "gmon_io.h"
#include "gp-gmon.h"
#include "corefile.h"
#include "data_pckts.h"
#include "call_graph.h"
#include "hist.h"
#include "symtab.h"
#include "cg_arcs.h"
#include "gp-experiment.h"
#include "collctrl.h"
#include "util.h"
#include "gp-defs.h"
class er_gmon : public DbeApplication
{
public:
er_gmon (int argc, char *argv[]);
virtual ~er_gmon ();
void start (int argc, char *argv[]);
private:
// override methods in base class
void usage ();
void usage_and_exit (int exit_code);
int check_mods (int argc, char *argv[]);
bool overwrite = false;
Coll_Ctrl *cc;
};
/*
* Default options values:
*/
#define HZ_WRONG 0
#define INC_DEPTH (128)
#define DFN_BUSY -1
#define DFN_NAN 0
#define CLOCK_TYPE OPROF_PCKT
/****/
#define ROOT_UID 801425552975190205ULL
#define ROOT_UID_INV 92251691606677ULL
#define ROOT_IDX 13907816567264074199ULL
#define ROOT_IDX_INV 2075111ULL
#define UIDTableSize 1048576
#define NATIVE_FRAME_BYTES(nframes) ( ((nframes)+1) * sizeof(long) )
#define OVERHEAD_BYTES ( 2 * sizeof(long) + 2 * sizeof(Stack_info) )
#define DEFAULT_MAX_NFRAMES 256
typedef struct
{
Sym *sym;
} DFN_Stack;
typedef struct CM_Array
{
unsigned int length; /* in bytes, not including length */
void *bytes;
} CM_Array;
typedef struct ClockPacket
{ /* clock profiling packet */
CM_Packet comm;
pthread_t lwp_id;
pthread_t thr_id;
uint32_t cpu_id;
hrtime_t tstamp __attribute__ ((packed));
uint64_t frinfo __attribute__ ((packed));
int mstate; /* kernel microstate */
int nticks; /* number of ticks in that state */
} ClockPacket;
static unsigned long *pc_array = NULL;
static char *base_folder;
static DFN_Stack *dfn_stack = NULL;
static int dfn_maxdepth = 0;
static int dfn_depth = 0;
static int pc_size = 0;
static int pc_maxdepth = 0;
static hrtime_t mytime = 0;
static uint64_t *UIDTable;
// forward declarations
static uint64_t get_frame_info (const CM_Array *array);
static uint64_t compute_uid (Frame_packet *frp);
static void writeBufferToFile (const CM_Packet *pckt,
const char *filename);
static int
real_main (int argc, char *argv[])
{
er_gmon *src = new er_gmon (argc, argv);
src->start (argc, argv);
delete src;
return 0;
}
/* Push a symbol into stack, increase size stack if too low. */
static void
pre_visit (Sym *sym)
{
++dfn_depth;
if (dfn_depth >= dfn_maxdepth)
{
dfn_maxdepth += INC_DEPTH;
dfn_stack = (DFN_Stack *) xrealloc (dfn_stack,
dfn_maxdepth * sizeof (*dfn_stack));
}
dfn_stack[dfn_depth - 1].sym = sym;
// Mark the input sym as busy
sym->cg.top_order = DFN_BUSY;
/* Mimic time. */
DBG (LOOKUPDEBUG,
printf (">>> Run for:%s ticks:%lf\n", sym->name, sym->hist.time));
DBG (LOOKUPDEBUG, printf (">>> Call chain:"));
pc_size = 0;
for (int i = (dfn_depth - 1); i >= 0; i--)
{
Sym *t = dfn_stack[i].sym;
++pc_size;
if (pc_size >= pc_maxdepth)
{
pc_maxdepth += INC_DEPTH;
pc_array = (unsigned long *)
xrealloc (pc_array, pc_maxdepth * sizeof (unsigned long));
}
pc_array[pc_size - 1] = t->addr;
DBG (LOOKUPDEBUG, printf ("----> %s: 0x%lx (%lf)\n", t->name,
t->addr, t->hist.time));
}
DBG (LOOKUPDEBUG, printf ("\n"));
ClockPacket pckt;
memset (&pckt, 0, sizeof (ClockPacket));
pckt.comm.type = CLOCK_TYPE;
pckt.comm.tsize = sizeof (ClockPacket);
pckt.lwp_id = 0xdeadbeef;
pckt.thr_id = 0xcafecafe;
pckt.cpu_id = 0;
pckt.tstamp = mytime++;
// Output `data.frameinfo` packet
CM_Array array;
array.length = pc_size * sizeof (unsigned long);
array.bytes = (void *) pc_array;
pckt.frinfo = get_frame_info (&array);
pckt.mstate = LMS_LINUX_CPU;
int calls = sym->ncalls > 0 ? sym->ncalls : 1;
double time = sym->hist.time > 0 ? sym->hist.time : 1;
uint64_t nticks = (uint64_t)(time / calls);
pckt.nticks = nticks > 0 ? nticks : 1;
// Output `profile` packet
writeBufferToFile ((CM_Packet*) &pckt, SP_PROFILE_FILE);
}
/* Take the last symbol out of the stack. */
static void
post_visit (Sym *sym)
{
sym->cg.top_order = DFN_NAN;
if (dfn_depth)
--dfn_depth;
}
static void
dfn (Sym *parent)
{
/* Detect cycles. */
if (parent->cg.top_order == DFN_BUSY)
{
parent->ncalls = 1;
pre_visit (parent);
return;
}
pre_visit (parent);
for (Arc *arc = parent->cg.children; arc; arc = arc->next_child)
{
dfn (arc->child);
}
post_visit(parent);
}
/* Go through all the symbols, start from the top of a call chain.
Mimimc a stack trace using a DF algorithm. */
static int
cg_traverse_arcs (const char *whoami)
{
Sym *sym;
Sym_Table *symtab = get_symtab (whoami);
for (sym = symtab->base; sym < symtab->limit; sym++)
{
/* Start DF algorithm from the top symbol. */
if (sym->cg.parents)
continue;
/* Skip unused symbols. */
if (!sym->cg.children)
continue;
dfn (sym);
}
return pc_size;
}
static void
writeBufferToFile (const CM_Packet *pckt,
const char *filename)
{
size_t size = pckt->tsize;
size_t asz;
char *buffer = (char *) pckt;
char *new_file_path;
FILE *outFile;
asz = strlen (base_folder) + strlen (filename) + 1 + 1;
new_file_path = (char *) alloca (asz);
snprintf (new_file_path, asz, "%s/%s", base_folder, filename);
// Open the file in binary mode
outFile = fopen (new_file_path, "ab");
if (!outFile)
{
perror ("Fopen failed");
exit (1);
}
// Write the buffer to the file
fwrite (buffer, size, 1, outFile);
fclose (outFile);
}
static uint64_t
compute_uid (Frame_packet *frp)
{
uint64_t idxs[LAST_INFO];
uint64_t uid = ROOT_UID;
uint64_t idx = ROOT_IDX;
Common_info *cinfo = (Common_info*) ((char*) frp + frp->hsize);
char *end = (char*) frp + frp->tsize;
for (;;)
{
if ((char*) cinfo >= end || cinfo->hsize == 0 ||
(char*) cinfo + cinfo->hsize > end)
break;
/* Start with a different value to avoid matching with uid */
uint64_t uidt = 1;
uint64_t idxt = 1;
long *ptr = (long*) ((char*) cinfo + cinfo->hsize);
long *bnd = (long*) ((char*) cinfo + sizeof (Common_info));
DBG (SAMPLEDEBUG,
printf( "compute_uid: Cnt=%ld: ", (long) cinfo->hsize));
while (ptr > bnd)
{
long val = *(--ptr);
DBG (SAMPLEDEBUG,
printf ("0x%8.8llx ", (unsigned long long) val));
uidt = (uidt + val) * ROOT_UID;
idxt = (idxt + val) * ROOT_IDX;
uid = (uid + val) * ROOT_UID;
idx = (idx + val) * ROOT_IDX;
}
if (cinfo->kind == STACK_INFO)
{
cinfo->uid = uidt;
idxs[cinfo->kind] = idxt;
}
cinfo = (Common_info*) ((char*) cinfo + cinfo->hsize);
}
DBG (SAMPLEDEBUG, printf ("\n"));
/* Check if we have already recorded that uid.
* The following fragment contains benign data races.
* It's important, though, that all reads from UIDTable
* happen before writes.
*/
int found1 = 0;
int idx1 = (int) ((idx >> 44) % UIDTableSize);
if (UIDTable[idx1] == uid)
found1 = 1;
int found2 = 0;
int idx2 = (int) ((idx >> 24) % UIDTableSize);
if (UIDTable[idx2] == uid)
found2 = 1;
int found3 = 0;
int idx3 = (int) ((idx >> 4) % UIDTableSize);
if (UIDTable[idx3] == uid)
found3 = 1;
if (!found1)
UIDTable[idx1] = uid;
if (!found2)
UIDTable[idx2] = uid;
if (!found3)
UIDTable[idx3] = uid;
if (found1 || found2 || found3)
{
return uid;
}
frp->uid = uid;
/* Compress info's */
cinfo = (Common_info*) ((char*) frp + frp->hsize);
for (;;)
{
if ((char*) cinfo >= end || cinfo->hsize == 0 ||
(char*) cinfo + cinfo->hsize > end)
break;
if (cinfo->kind == STACK_INFO || cinfo->kind == JAVA_INFO)
{
long *ptr = (long*) ((char*) cinfo + sizeof (Common_info));
long *bnd = (long*) ((char*) cinfo + cinfo->hsize);
uint64_t uidt = cinfo->uid;
uint64_t idxt = idxs[cinfo->kind];
int found = 0;
int first = 1;
while (ptr < bnd - 1)
{
int idx1 = (int) ((idxt >> 44) % UIDTableSize);
if (UIDTable[idx1] == uidt)
{
found = 1;
break;
}
else if (first)
{
first = 0;
UIDTable[idx1] = uidt;
}
long val = *ptr++;
uidt = uidt * ROOT_UID_INV - val;
idxt = idxt * ROOT_IDX_INV - val;
}
if (found)
{
char *d = (char*) ptr;
char *s = (char*) bnd;
if (!first)
{
int i;
for (i = 0; i < (int) sizeof (uidt); i++)
{
*d++ = (char) uidt;
uidt = uidt >> 8;
}
}
int delta = s - d;
while (s < end)
*d++ = *s++;
cinfo->kind |= COMPRESSED_INFO;
cinfo->hsize -= delta;
frp->tsize -= delta;
end -= delta;
}
}
cinfo = (Common_info*) ((char*) cinfo + cinfo->hsize);
}
writeBufferToFile ((CM_Packet*) frp, "data.frameinfo");
return uid;
}
static uint64_t
get_frame_info (const CM_Array *array)
{
int max_native_nframes = DEFAULT_MAX_NFRAMES;
if (array == NULL || array->length <= 0)
return 0;
int max_frame_size = OVERHEAD_BYTES + NATIVE_FRAME_BYTES (max_native_nframes);
Frame_packet *frpckt = (Frame_packet *) alloca (sizeof (Frame_packet) + max_frame_size);
frpckt->type = FRAME_PCKT;
frpckt->hsize = sizeof (Frame_packet);
char *d = (char*) (frpckt + 1);
int size = max_frame_size;
/* create a stack image from user data */
Stack_info *sinfo = (Stack_info*) d;
int sz = sizeof (Stack_info);
d += sz;
size -= sz;
sz = array->length;
if (sz > size)
sz = size; // YXXX should we mark this with truncation frame?
memcpy (d, array->bytes, sz);
d += sz;
size -= sz;
sinfo->kind = STACK_INFO;
sinfo->hsize = (d - (char*) sinfo);
/* Compute the total size */
frpckt->tsize = d - (char*) frpckt;
return compute_uid (frpckt);
}
/* Generate the log.xml file. */
static void
gen_gmon_log (void)
{
FILE *logx;
char *new_file_path;
size_t asz = strlen (base_folder) + strlen (SP_LOG_FILE) + 1 + 1;
new_file_path = (char *) alloca (asz);
snprintf (new_file_path, asz, "%s/%s", base_folder, SP_LOG_FILE);
logx = fopen (new_file_path, "w");
long hz = hist_get_hz ();
int gmon_interval = (hz == HZ_WRONG ? 1000 : hz * 100);
fprintf (logx, "<profile name=\"%s\" ptimer=\"%d\" numstates=\"%d\">\n",
SP_JCMD_PROFILE, gmon_interval, LMS_MAGIC_ID_LINUX);
fprintf (logx, " <profdata fname=\"profile\"/>\n");
/* Record Profile packet description */
fprintf (logx, " <profpckt kind=\"%d\" uname=\"Clock profiling data\">\n",
CLOCK_TYPE);
fprintf (logx, " <field name=\"LWPID\" uname=\"Lightweight process id\" \
offset=\"%d\" type=\"%s\"/>\n",
(int) offsetof (ClockPacket, lwp_id),
fld_sizeof (ClockPacket, lwp_id) == 4 ? "INT32" : "INT64");
fprintf (logx, " <field name=\"THRID\" uname=\"Thread number\" \
offset=\"%d\" type=\"%s\"/>\n",
(int) offsetof (ClockPacket, thr_id),
fld_sizeof (ClockPacket, thr_id) == 4 ? "INT32" : "INT64");
fprintf (logx, " <field name=\"CPUID\" uname=\"CPU id\" offset=\"%d\" \
type=\"%s\"/>\n",
(int) offsetof (ClockPacket, cpu_id),
fld_sizeof (ClockPacket, cpu_id) == 4 ? "INT32" : "INT64");
fprintf (logx, " <field name=\"TSTAMP\" uname=\"High resolution \
timestamp\" offset=\"%d\" type=\"%s\"/>\n",
(int) offsetof (ClockPacket, tstamp),
fld_sizeof (ClockPacket, tstamp) == 4 ? "INT32" : "INT64");
fprintf (logx, " <field name=\"FRINFO\" offset=\"%d\" type=\"%s\"/>\n",
(int) offsetof (ClockPacket, frinfo),
fld_sizeof (ClockPacket, frinfo) == 4 ? "INT32" : "INT64");
fprintf (logx, " <field name=\"MSTATE\" uname=\"Thread state\" \
offset=\"%d\" type=\"%s\"/>\n",
(int) offsetof (ClockPacket, mstate),
fld_sizeof (ClockPacket, mstate) == 4 ? "INT32" : "INT64");
fprintf (logx, " <field name=\"NTICK\" uname=\"Duration\" offset=\"%d\" \
type=\"%s\"/>\n",
(int) offsetof (ClockPacket, nticks),
fld_sizeof (ClockPacket, nticks) == 4 ? "INT32" : "INT64");
fprintf (logx, " </profpckt>\n");
fprintf (logx,"</profile>\n");
fprintf (logx, "<event kind=\"%s\" tstamp=\"%u.%09u\" time=\"%lld\" \
tm_zone=\"%lld\"/>\n",
SP_JCMD_RUN, 0, 0, 0LL, 0LL);
fclose (logx);
}
/* This will not work for PIE execs. */
static void
gen_gmon_map (char *name)
{
bfd_vma mpage, page_size = sysconf(_SC_PAGESIZE);
bfd_vma loadaddr, vaddr = core_text_sect->vma; //lma?
bfd_size_type msize = core_text_sect->size;
int timestamp = 1;
int offset = 0;
int check = -1;
int modeflags = PROT_READ | PROT_EXEC; //0x05
char *new_file_path;
size_t asz = strlen (base_folder) + strlen (SP_MAP_FILE) + 1 + 1;
new_file_path = (char *) alloca (asz);
snprintf (new_file_path, asz, "%s/%s", base_folder, SP_MAP_FILE);
FILE *mapx = fopen (new_file_path, "w");
if (mapx == NULL) {
return;
}
/* alignment mask. */
mpage = ~(page_size - 1);
// Round down to page size alignment
loadaddr = vaddr & mpage;
// Compensate for the alignment gain
msize += vaddr - loadaddr;
// Round up to a multiple of page size;
msize = (msize + page_size - 1) & mpage;
fprintf (mapx, "<event kind=\"map\" object=\"segment\" tstamp=\"%u.%09u\" "
"vaddr=\"0x%016llX\" size=\"%llu\" pagesz=\"%d\" foffset=\"%c0x%08llX\" "
"modes=\"0x%03X\" chksum=\"0x%0X\" name=\"%s\"/>\n",
(unsigned) (timestamp / NANOSEC),
(unsigned) (timestamp % NANOSEC),
(long long unsigned) loadaddr, (long long unsigned) msize,
(int) page_size, offset < 0 ? '-' : '+',
(long long unsigned) (offset < 0 ? -offset : offset),
modeflags, check, name);
fclose (mapx);
}
static int
checkflagterm (const char *c)
{
if (c[2] != 0)
{
dbe_write (2, GTXT ("gmon: unrecognized argument `%s'\n"), c);
return -1;
}
return 0;
}
static void
writeStr (int f, const char *buf)
{
if (buf != NULL)
write (f, buf, strlen (buf));
}
/* main entry point. It calls catch_out_of_memory which will call
real_main() function. */
int
main (int argc, char *argv[])
{
xmalloc_set_program_name (argv[0]);
return catch_out_of_memory (real_main, argc, argv);
}
er_gmon::er_gmon (int argc, char *argv[]) : DbeApplication (argc, argv)
{
int sz = UIDTableSize * sizeof (*UIDTable);
UIDTable = (uint64_t *) xmalloc (sz);
if (!UIDTable)
return;
memset (UIDTable, 0, sz);
overwrite = false;
cc = NULL;
}
void
er_gmon::start (int argc, char *argv[])
{
char *gmon_name;
char *a_out_name;
// Create a collector control structure, disabling aggressive
// warning.
cc = new Coll_Ctrl (0, false, false);
cc->set_default_stem ("gmon");
whoami = argv[0];
if (argc > 1 && strncmp (argv[1], NTXT ("--whoami="), 9) == 0)
{
whoami = argv[1] + 9;
argc--;
argv++;
}
if (argc == 2 && strcmp (argv[1], NTXT ("-h")) == 0)
{
/* only one argument, -h */
usage ();
}
else if (argc == 2 && (strcmp (argv[1], NTXT ("-help")) == 0 ||
strcmp (argv[1], NTXT ("--help")) == 0))
{
/* only one argument, -help or --help */
usage ();
}
else if ((argc == 2) &&
(strcmp (argv[1], NTXT ("--version")) == 0))
{
/* only one argument, --version */
/* print the version info */
Application::print_version_info ();
exit (0);
}
int adj = check_mods (argc, argv);
if (adj < 0)
{
usage_and_exit (1);
}
char *ret = cc->create_exp_dir ();
if (ret != NULL)
{
dbe_write (2, NTXT ("%s\n"), ret);
free (ret);
exit (1);
}
// Get the file path.
base_folder = cc->get_experiment ();
// Default names
a_out_name = xstrdup ("a.out");
gmon_name = xstrdup ("gmon.out");
// Get the ELF and the GMON.OUT file if they exist.
if (argc == (adj + 1))
{
a_out_name = argv[adj];
}
else if (argc == (adj + 2))
{
a_out_name = argv[adj++];
gmon_name = argv[adj];
}
else if (argc != adj)
{
usage_and_exit (1);
}
/* Read the elf syms and the gmon file. */
if (core_init (a_out_name, whoami) < 0)
{
cc->remove_exp_dir ();
exit (1);
}
if (gmon_out_read (gmon_name, FF_AUTO, whoami) < 0)
{
cc->remove_exp_dir ();
exit (1);
}
/* Process the gmon file, and output the gprofng project files. */
hist_assign_samples (whoami);
cg_traverse_arcs (whoami);
gen_gmon_map (a_out_name);
gen_gmon_log ();
}
/* Get the args and search for modifiers. */
int
er_gmon::check_mods (int argc, char *argv[])
{
char *expName = NULL;
int i = -1;
for (i = 1; i < argc; i++)
{
if (argv[i] == NULL)
break;
if (argv[i][0] != '-')
break;
switch (argv[i][1])
{
case 'O':
overwrite = true;
//FALLTHROU
case 'o':
if (checkflagterm (argv[i]) == -1)
return -1;
if (argv[i + 1] == NULL)
{
dbe_write (2,
GTXT ("Argument %s must be followed by a file name\n"),
argv[i]);
return -1;
}
if (expName != NULL)
{
dbe_write (2, GTXT ("Only one -o or -O argument may be used\n"));
return -1;
}
expName = argv[i + 1];
i++;
break;
default:
dbe_write (2, GTXT ("gmon: unrecognized argument `%s'\n"),
argv[i]);
return -1;
}
}
if (expName)
{
char *ccret;
char *ccwarn = NULL;
ccret = cc->set_expt (expName, &ccwarn, overwrite);
if (ccwarn)
{
writeStr (2, ccwarn);
free (ccwarn);
}
if (ccret)
{
writeStr (2, ccret);
return -1;
}
}
return i;
}
void
er_gmon::usage ()
{
usage_and_exit (0);
}
void
er_gmon::usage_and_exit (int exit_code)
{
printf ( GTXT (
"Usage: gprofng display gmon [OPTION(S)] [TARGET-OBJECT [GMON-FILE]]\n"));
printf ( GTXT (
"\n"
"Convert an gmon.out file to a gprofng experiment.\n"
"\n"
"Options:\n"
"\n"
" --version print the version number and exit.\n"
" -h/--help print usage information and exit.\n"
"\n"
" -o <exp_name> specify the name for (and path to) the experiment directory; the\n"
" the default path is the current directory.\n"
"\n"
" -O <exp_name> the same as -o, but unlike the -o option, silently overwrite an\n"
" existing experiment directory with the same name.\n"
"\n"));
exit (exit_code);
}
er_gmon::~er_gmon ()
{
free (UIDTable);
delete cc;
}