/*
 * Copyright 2000-2004 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

#include <stdio.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <libelf.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/param.h>
#include <stdlib.h>
#include <thread.h>
#include <synch.h>
#include <stdarg.h>
#include <unistd.h>

#define TRUE    1
#define FALSE   0

/* 32/64 bit build issues. */

#ifdef _LP64
 #define ElfXX_Sym      Elf64_Sym
 #define ElfXX_Ehdr     Elf64_Ehdr
 #define ElfXX_Shdr     Elf64_Shdr
 #define elfXX_getehdr  elf64_getehdr
 #define ElfXX_Addr     Elf64_Addr
 #define ELFXX_ST_TYPE  ELF64_ST_TYPE
 #define ELFXX_ST_BIND  ELF64_ST_BIND
 #define elfXX_getshdr  elf64_getshdr
#else
 #define ElfXX_Sym      Elf32_Sym
 #define ElfXX_Ehdr     Elf32_Ehdr
 #define ElfXX_Shdr     Elf32_Shdr
 #define elfXX_getehdr  elf32_getehdr
 #define ElfXX_Addr     Elf32_Addr
 #define ELFXX_ST_TYPE  ELF32_ST_TYPE
 #define ELFXX_ST_BIND  ELF32_ST_BIND
 #define elfXX_getshdr  elf32_getshdr
#endif

extern void *_getReturnAddr(void);



typedef struct StabEntry {
    unsigned      n_strx;
    unsigned char n_type;
    char          n_other;
    short         n_desc;
    unsigned      n_value;
} StabEntry;


typedef struct SymChain {
    struct SymChain *next;
    ElfXX_Sym *sym;
} SymChain;


typedef struct ObjFileList {
    struct ObjFileList *next;
    const char *objFileName;
    int    nameLen;
} ObjFileList;


typedef struct ElfInfo {
    const char *fullName;
    const char *baseName;
    FILE       *outFile;
    int        fd;
    Elf        *elf;
    Elf_Data   *sectionStringData;
    Elf_Data   *symData;
    Elf_Data   *symStringData;
    int        symCount;
    SymChain   *symChainHead;
    Elf_Data   *stabData;
    Elf_Data   *stabStringData;
    int        stabCount;
    ObjFileList *objFileList;
} ElfInfo;



#define COUNT_BUF_SIZE (16*1024*1024)

#define ENTRY_CHAIN_BUCKETS  4999

static int *countBuf;
static void *textOffset;
static const char *libFileName;



static void fail(const char *err, ...)
{
    va_list ap;
    va_start(ap, err);
    vfprintf(stderr, err, ap);
    fflush(stderr);
    va_end(ap);
}



static const char *getSymString(ElfInfo *elfInfo, int index)
{
    return (const char *)elfInfo->symStringData->d_buf + index;
}


static const char *getStabString(ElfInfo *elfInfo, int index)
{
    return (const char *)elfInfo->stabStringData->d_buf + index;
}


static const char *getSectionString(ElfInfo *elfInfo, int index)
{
    return (const char *)elfInfo->sectionStringData->d_buf + index;
}


static const char *makeObjFileList(ElfInfo *elfInfo)
{
    int i;
    const char *file;
    unsigned offset, lastOffset;
    ObjFileList *objFileList;

    file = NULL;
    offset = lastOffset = 0;
    for (i = 0; i < elfInfo->stabCount; ++i) {
        StabEntry *stab = ((StabEntry *)elfInfo->stabData->d_buf) + i;

        if (stab->n_type == 0 /* N_UNDEF */) {
            offset = lastOffset;
            lastOffset += stab-> n_value;
        }
        else if (stab->n_type == 0x38 /* N_OBJ */) {
            file = getStabString(elfInfo, stab->n_strx + offset);
            objFileList = (ObjFileList *)malloc(sizeof (ObjFileList));
            objFileList->objFileName = file;
            /*fprintf(stderr,"new obj file %s.\n", file);*/
            objFileList->nameLen = strlen(file);
            objFileList->next = elfInfo->objFileList;
            elfInfo->objFileList = objFileList;
        }
    }
    return NULL;
}


static ElfInfo *createElfInfo(const char *fullName)
{
    ElfInfo    *elfInfo;
    ElfXX_Ehdr *ehdr;
    Elf_Scn    *sectionStringSection;
    Elf_Scn    *stringSection;
    Elf_Scn    *symSection;
    ElfXX_Shdr *symHeader;
    Elf_Scn    *stabSection;
    ElfXX_Shdr *stabHeader;
    ElfXX_Shdr *stringHeader;
    Elf        *elf;
    const char *p;

    /*fprintf(stderr, "# mapfile info for %s.\n", fullName);*/
    elfInfo = (ElfInfo *)malloc(sizeof (ElfInfo));
    memset(elfInfo, 0, sizeof (ElfInfo));
    elfInfo->fullName = strdup(fullName);
    p = rindex(elfInfo->fullName, '/');
    elfInfo->baseName = (p == NULL) ? elfInfo->fullName : p + 1;

    /* Open the ELF file. Get section headers. */

    elf_version(EV_CURRENT);
    elfInfo->fd = open(fullName, O_RDONLY);
    if (elfInfo->fd < 0)
        fail("Unable to open ELF file %s.\n", fullName);
    elf = elf_begin(elfInfo->fd, ELF_C_READ, (Elf *)0);
    if (elf == NULL)
        fail("elf_begin failed.\n");
    ehdr = elfXX_getehdr(elf);
    sectionStringSection = elf_getscn(elf, ehdr->e_shstrndx);
    elfInfo->sectionStringData = elf_getdata(sectionStringSection, NULL);

    /* Find the symbol table section. */

    symSection = NULL;
    while ((symSection = elf_nextscn(elf, symSection)) != NULL) {
        symHeader = elfXX_getshdr(symSection);
        p = getSectionString(elfInfo, symHeader->sh_name);
        if (strcmp(p, ".symtab") == 0)
            break;
    }
    if (symSection == NULL)
        fail("Unable to find symbol table.\n");

    elfInfo->symData = elf_getdata(symSection, NULL);
    elfInfo->symCount = elfInfo->symData->d_size / sizeof (ElfXX_Sym);

    /* Find the string section. */

    stringSection = NULL;
    while ((stringSection = elf_nextscn(elf, stringSection)) != NULL) {
        stringHeader = elfXX_getshdr(stringSection);
        p = getSectionString(elfInfo, stringHeader->sh_name);
        if (strcmp(p, ".strtab") == 0)
            break;
    }
    if (stringSection == NULL)
        fail("Unable to find string table.\n");

    elfInfo->symStringData = elf_getdata(stringSection, NULL);
    elfInfo->symChainHead = NULL;

    /* Find the stab section. */

    stabSection = NULL;
    while ((stabSection = elf_nextscn(elf, stabSection)) != NULL) {
        stabHeader = elfXX_getshdr(stabSection);
        p = getSectionString(elfInfo, stabHeader->sh_name);
        if (strcmp(p, ".stab.index") == 0)
            break;
    }
    if (stabSection == NULL)
        fail("Unable to find .stab.index.\n");

    elfInfo->stabData = elf_getdata(stabSection, NULL);
    elfInfo->stabCount = elfInfo->stabData->d_size / sizeof (StabEntry);

    /* Find the string section. */

    stringSection = NULL;
    while ((stringSection = elf_nextscn(elf, stringSection)) != NULL) {
        stringHeader = elfXX_getshdr(stringSection);
        p = getSectionString(elfInfo, stringHeader->sh_name);
        if (strcmp(p, ".stab.indexstr") == 0)
            break;
    }
    if (stringSection == NULL)
        fail("Unable to find .stab.indexstr table.\n");

    elfInfo->stabStringData = elf_getdata(stringSection, NULL);
    makeObjFileList(elfInfo);

    return elfInfo;
}


static const char *identifyFile(ElfInfo *elfInfo, const char *name)
{
    int i;
    const char *file;
    const char *sourceFile;
    unsigned offset, lastOffset;
    const char *lastOptions;
    char *buf;

    file = NULL;
    lastOptions = NULL;
    offset = lastOffset = 0;
    for (i = 0; i < elfInfo->stabCount; ++i) {
        StabEntry *stab = ((StabEntry *)elfInfo->stabData->d_buf) + i;

        if (stab->n_type == 0 /* N_UNDEF */) {
            offset = lastOffset;
            lastOffset += stab-> n_value;
            file = NULL;   /* C++ output files seem not to have N_OBJ fields.*/
            lastOptions = NULL;
            sourceFile = getStabString(elfInfo, stab->n_strx + offset);
        }
        else if (stab->n_type == 0x24 /* N_FUN */) {
            const char *stabName;
            char *p1, *p2;

            stabName = getStabString(elfInfo, stab->n_strx + offset);
            if (strcmp (stabName, name) == 0) {

                if (file != NULL)
                    return file;

                if (lastOptions == NULL)
                    return NULL;

                p1 = strstr(lastOptions, ";ptr");
                if (p1 == NULL)
                    return NULL;
                p1 += 4;
                p2 = index(p1, ';');
                if (p2 == NULL)
                    return NULL;

                buf = (char *)malloc(p2 - p1 + strlen(sourceFile) + 10);
                strncpy(buf, p1, p2 - p1);
                buf[p2-p1] = '/';
                strcpy(buf + (p2 - p1) + 1, sourceFile);
                p1 = rindex(buf, '.');
                if (p1 == NULL)
                    return NULL;
                p1[1] = 'o';
                p1[2] = '\0';
                return buf;
            }
        }
        else if (stab->n_type == 0x3c /* N_OPT */) {
            lastOptions =  getStabString(elfInfo, stab->n_strx + offset);
        }
        else if (stab->n_type == 0x38 /* N_OBJ */) {
            file = getStabString(elfInfo, stab->n_strx + offset);
        }
    }
    return NULL;
}


static const char *checkObjFileList(ElfInfo *elfInfo, const char *file) {
    ObjFileList *objFileList;
    int len = strlen(file);
    int nameLen;
    const char *objFileName;

    /*fprintf(stderr, "checkObjFileList(%s).\n", file);*/
    for (objFileList = elfInfo->objFileList; objFileList != NULL;
         objFileList = objFileList->next) {

        objFileName = objFileList->objFileName;
        nameLen = objFileList->nameLen;
        if (strcmp(objFileName +nameLen - len, file) != 0)
            continue;

        if (len == nameLen)
            return file;

        if (len > nameLen)
            continue;

        if (*(objFileName + nameLen - len - 1) == '/')
            return objFileName;
    }
    return file;
}


static void identifySymbol(ElfInfo *elfInfo, ElfXX_Addr value, int count)
{
    int i;
    ElfXX_Sym *bestFunc = NULL;
    ElfXX_Sym *bestFile = NULL;
    ElfXX_Sym *lastFile = NULL;
    char fileName[MAXPATHLEN];
    char buf[4096];
    const char *file;
    SymChain *chain;
    const char *format;

    for (i = 0; i < elfInfo->symCount; ++i) {
        ElfXX_Sym *sym = ((ElfXX_Sym *)elfInfo->symData->d_buf) + i;
        if (ELFXX_ST_TYPE(sym->st_info) == STT_FUNC) {

            if (sym->st_shndx == SHN_UNDEF)
                continue;

            if (sym->st_value > value)
                continue;

            if (bestFunc != NULL) {

                if (sym->st_value < bestFunc->st_value)
                    continue;

                /*
                 * If we have two symbols of equal value, we have a problem -
                 * we must pick the "right" one, which is the one the compiler
                 * used to generate the section name with -xF.
                 *
                 * The compiler has the nasty habit of generating two
                 * mangled names for some C++ functions.
                 *
                 * Try - picking the shortest name.
                 */

                if (sym->st_value == bestFunc->st_value) {
                    if (strlen(getSymString(elfInfo, bestFunc->st_name)) <
                        strlen(getSymString(elfInfo, sym->st_name)))
                        continue;
                }

            }
            bestFunc = sym;
            bestFile = lastFile;
        }
        else if (ELFXX_ST_TYPE(sym->st_info) == STT_FILE) {
            lastFile = sym;
        }
    }

    if (bestFunc == NULL)
        fail("Unable to find symbol for address 0x%x.\n", value);

    for (chain = elfInfo->symChainHead; chain != NULL; chain = chain->next) {
        if (chain->sym == bestFunc)
            return;
    }
    chain = (SymChain *)malloc(sizeof (SymChain));
    chain->sym = bestFunc;
    chain->next = elfInfo->symChainHead;
    elfInfo->symChainHead = chain;


    if (ELFXX_ST_BIND(bestFunc->st_info) == STB_GLOBAL)
        file = "";
    else {
        const char *name = getSymString(elfInfo, bestFunc->st_name);
        file = identifyFile(elfInfo, name);
        if (file == NULL) {
            if (bestFile == NULL) {
                file = "notFound";
                fail("Failed to identify %s.\n", name);
            } else {
                char *suffix;
                fileName[0] = ':';
                fileName[1] = ' ';
                file = getSymString(elfInfo, bestFile->st_name);
                strncpy(fileName+2, file, MAXPATHLEN-3);
                suffix = rindex(fileName, '.');
                if (suffix == NULL)
                    fail("no file name suffix?");
                suffix[1] = 'o';
                suffix[2] = '\0';

                file = checkObjFileList(elfInfo, fileName+2);
                if (file != fileName+2)
                    strncpy(fileName+2, file, MAXPATHLEN-3);

                file = fileName;
            }
        } else {
            fileName[0] = ':';
            fileName[1] = ' ';
            strncpy(fileName + 2, file, MAXPATHLEN-3);
            file = fileName;
        }
    }
    format = "text: .text%%%s%s;\n";
    i = snprintf(buf, sizeof buf, format,
            bestFunc ? getSymString(elfInfo, bestFunc->st_name) : "notFound",
            file);
    write(2, buf, i);
}


static mutex_t mutex;
static int orderByCount = FALSE;


static void init_mcount(void)
{
    mutex_init(&mutex, USYNC_THREAD, NULL);
}

#pragma init(init_mcount)


typedef struct CountAddrPair {
    int          count;
    unsigned int addr;
} CountAddrPair;


static int compareCounts(const void *a, const void *b) {
    return ((CountAddrPair *)b)->count - ((CountAddrPair *)a)->count;
}

static int compareCountsReverse(const void *a, const void *b) {
    return ((CountAddrPair *)a)->count - ((CountAddrPair *)b)->count;
}


static void doCounts(void) {
    unsigned int i;
    int n;
    int nMethods;
    int nMethods2;
    CountAddrPair *pairs;
    ElfInfo *elfInfo;

    elfInfo = createElfInfo(libFileName);

    nMethods = 0;
    for (i = 0; i < COUNT_BUF_SIZE >> 2; ++i) {
        n = countBuf[i];
        if (n > 0)
            ++nMethods;
    }
    pairs = (CountAddrPair *)malloc(sizeof(CountAddrPair) * nMethods);
    nMethods2 = 0;
    for (i = 0; i < COUNT_BUF_SIZE >> 2; ++i) {
        n = countBuf[i];
        if (n > 0) {
            pairs[nMethods2].count = n;
            pairs[nMethods2].addr = i << 2;
            ++nMethods2;
            if (nMethods2 > nMethods) {
                fprintf(stderr, "Number of methods detected increased after"
                        " the atexit call.\n");
                break;
            }
        }
    }
    if (orderByCount) {
        qsort(pairs, nMethods, sizeof pairs[0], &compareCounts);
        for (i = 0; i < nMethods; ++i) {
            identifySymbol(elfInfo, pairs[i].addr, pairs[i].count);
        }
    }
    else {
        qsort(pairs, nMethods, sizeof pairs[0], &compareCountsReverse);
        for (i = 0; i < nMethods; ++i) {
            identifySymbol(elfInfo, pairs[i].addr, 0);
        }
    }
}


static void __mcount(void *i0)
{
    Dl_info info;
    unsigned int offset;
    int *p;
    static int callsCounted = 0;
    static int initialized = FALSE;

    if (!initialized) {
        dladdr(i0, &info);
        libFileName = info.dli_fname;
#if 0
        fprintf(stderr, "Profiling %s\n", libFileName);
#endif
        textOffset = info.dli_fbase;
        if (getenv("MCOUNT_ORDER_BY_COUNT") != NULL) {
            orderByCount = TRUE;
        }
        countBuf = (int *)malloc(COUNT_BUF_SIZE);
        memset(countBuf, 0, COUNT_BUF_SIZE);
        atexit(&doCounts);
        initialized = TRUE;
    }

    if ((uintptr_t)i0 < (uintptr_t)textOffset) {
        fprintf(stderr, "mcount: function being profiled out of range????\n");
        fprintf(stderr, "        profiling more than one library at once????\n");
#if 0
        dladdr(i0, &info);
        fprintf(stderr, "Problem with %s in %s ???\n",
                info.dli_sname, info.dli_fname);
#endif
        fflush(stderr);
        exit(666);
    }
    offset = ((uintptr_t)i0) - ((uintptr_t)textOffset);
    if (offset > COUNT_BUF_SIZE) {
        fprintf(stderr, "mcount: internal buffer too small for test.\n");
        fprintf(stderr, "     or function being profiled out of range????\n");
        fprintf(stderr, "     or profiling more than one library at once????\n");
#if 0
        dladdr(i0, &info);
        fprintf(stderr, "Problem with %s in %s ???\n",
                info.dli_sname, info.dli_fname);
#endif
        fflush(stderr);
        exit(666);
    }

    p = &countBuf[offset >>2];
    if (orderByCount) {
        ++*p;
    }
    else {
        if (*p == 0) {
            *p = ++callsCounted;
        }
    }
}


void _mcount(void)
{
    __mcount(_getReturnAddr());
}


void mcount(void)
{
    __mcount(_getReturnAddr());
}
