/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * Copyright (C) 2024 KylinSoft  Co., Ltd.
 *
 * 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 2 of the License, 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */
#ifndef __ADDR_ANY_H__
#define __ADDR_ANY_H__


//linux
#include <regex.h>
#include <cxxabi.h>
//c
#include <cinttypes>
#include <cstdio>
#include <cstdlib>

//c++
#include <string>
#include <map>
//project
#include "elfio.hpp"



class AddrAny
{
public:
    AddrAny()
    {
        m_init = get_exe_pathname(m_fullname);
        m_baseaddr = 0;
    }
    AddrAny(std::string libname)
    {
        m_init = get_lib_pathname_and_baseaddr(libname, m_fullname, m_baseaddr);
    }

    int get_local_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
    {
        return get_func_addr(SHT_SYMTAB, STB_LOCAL, func_name_regex_str, result);
    }
    int get_global_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
    {
        return get_func_addr(SHT_SYMTAB, STB_GLOBAL, func_name_regex_str, result);
    }
    int get_weak_func_addr_symtab(std::string func_name_regex_str, std::map<std::string,void*>& result)
    {
        return get_func_addr(SHT_SYMTAB, STB_WEAK, func_name_regex_str, result);
    }
    
    int get_global_func_addr_dynsym( std::string func_name_regex_str, std::map<std::string,void*>& result)
    {
        return get_func_addr(SHT_DYNSYM, STB_GLOBAL, func_name_regex_str, result);
    }
    int get_weak_func_addr_dynsym(std::string func_name_regex_str, std::map<std::string,void*>& result)
    {
        return get_func_addr(SHT_DYNSYM, STB_WEAK, func_name_regex_str, result);
    }
    
private:
    bool demangle(std::string& s, std::string& name) {
        int status;
        char* pname = abi::__cxa_demangle(s.c_str(), 0, 0, &status);
        if (status != 0)
        {
            switch(status)
            {
                case -1: name = "memory allocation error"; break;
                case -2: name = "invalid name given"; break;
                case -3: name = "internal error: __cxa_demangle: invalid argument"; break;
                default: name = "unknown error occured"; break;
            }
            return false;
        }
        name = pname;
        free(pname);
        return true;
    }
    bool get_exe_pathname( std::string& name)
    {
        char                     line[512];
        FILE                    *fp;
        uintptr_t                base_addr;
        char                     perm[5];
        unsigned long            offset;
        int                      pathname_pos;
        char                    *pathname;
        size_t                   pathname_len;
        int                      match = 0;
        
        if(NULL == (fp = fopen("/proc/self/maps", "r")))
        {
            return false;
        }

        while(fgets(line, sizeof(line), fp))
        {
            if(sscanf(line, "%" PRIxPTR "-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) continue;

            if(0 != offset) continue;

            //get pathname
            while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1))
                pathname_pos += 1;
            if(pathname_pos >= (int)(sizeof(line) - 1)) continue;
            pathname = line + pathname_pos;
            pathname_len = strlen(pathname);
            if(0 == pathname_len) continue;
            if(pathname[pathname_len - 1] == '\n')
            {
                pathname[pathname_len - 1] = '\0';
                pathname_len -= 1;
            }
            if(0 == pathname_len) continue;
            if('[' == pathname[0]) continue;

            name = pathname;
            match = 1;
            break;

        }
        fclose(fp);

        if(0 == match)
        {
            return false;
        }
        else
        {
            return true;
        }

    }

    bool get_lib_pathname_and_baseaddr(std::string pathname_regex_str, std::string& name, unsigned long& addr)
    {
        char                     line[512];
        FILE                    *fp;
        uintptr_t                base_addr;
        char                     perm[5];
        unsigned long            offset;
        int                      pathname_pos;
        char                    *pathname;
        size_t                   pathname_len;
        int                      match;
        regex_t   pathname_regex;

        regcomp(&pathname_regex, pathname_regex_str.c_str(), 0);

        if(NULL == (fp = fopen("/proc/self/maps", "r")))
        {
            return false;
        }

        while(fgets(line, sizeof(line), fp))
        {
            if(sscanf(line, "%" PRIxPTR "-%*lx %4s %lx %*x:%*x %*d%n", &base_addr, perm, &offset, &pathname_pos) != 3) continue;

            //check permission
            if(perm[0] != 'r') continue;
            if(perm[3] != 'p') continue; //do not touch the shared memory

            //check offset
            //
            //We are trying to find ELF header in memory.
            //It can only be found at the beginning of a mapped memory regions
            //whose offset is 0.
            if(0 != offset) continue;

            //get pathname
            while(isspace(line[pathname_pos]) && pathname_pos < (int)(sizeof(line) - 1))
                pathname_pos += 1;
            if(pathname_pos >= (int)(sizeof(line) - 1)) continue;
            pathname = line + pathname_pos;
            pathname_len = strlen(pathname);
            if(0 == pathname_len) continue;
            if(pathname[pathname_len - 1] == '\n')
            {
                pathname[pathname_len - 1] = '\0';
                pathname_len -= 1;
            }
            if(0 == pathname_len) continue;
            if('[' == pathname[0]) continue;

            //check pathname
            //if we need to hook this elf?
            match = 0;
            if(0 == regexec(&pathname_regex, pathname, 0, NULL, 0))
            {
                match = 1;
                name = pathname;
                addr = (unsigned long)base_addr;
                break;
            }
            if(0 == match) continue;

        }
        fclose(fp);
        if(0 == match)
        {
            return false;
        }
        else
        {
            return true;
        }

    }

    int get_func_addr(unsigned int ttype, unsigned int stype, std::string& func_name_regex_str, std::map<std::string,void*>& result)
    {
        // Create an elfio reader
        ELFIO::elfio reader;
        int count = 0;
        regex_t   pathname_regex;

        if(!m_init)
        {
            return -1;
        }

        regcomp(&pathname_regex, func_name_regex_str.c_str(), 0);
        // Load ELF data
        if(!reader.load(m_fullname.c_str()))
        {
            return -1;
        }
        
        ELFIO::Elf_Half sec_num = reader.sections.size();
        for(int i = 0; i < sec_num; ++i)
        {
            ELFIO::section* psec = reader.sections[i];
            // Check section type
            if(psec->get_type() == ttype)
            {
                const ELFIO::symbol_section_accessor symbols( reader, psec );
                for ( unsigned int j = 0; j < symbols.get_symbols_num(); ++j )
                {
                    std::string name;
                    std::string name_mangle;
                    ELFIO::Elf64_Addr value;
                    ELFIO::Elf_Xword size;
                    unsigned char bind;
                    unsigned char type;
                    ELFIO::Elf_Half section_index;
                    unsigned char other;
                    
                    // Read symbol properties
                    symbols.get_symbol( j, name, value, size, bind, type, section_index, other );
                    if(type == STT_FUNC && bind == stype)
                    {
                        bool ret = demangle(name,name_mangle);
                        if(ret == true)
                        {
                            if (0 == regexec(&pathname_regex, name_mangle.c_str(), 0, NULL, 0))
                            {
                                  result.insert ( std::pair<std::string,void *>(name_mangle,(void*)(value + m_baseaddr)));
                                  count++;
                            }
                        }
                        else
                        {
                            if (0 == regexec(&pathname_regex, name.c_str(), 0, NULL, 0))
                            {
                                  result.insert ( std::pair<std::string,void *>(name,(void*)(value + m_baseaddr)));
                                  count++;
                            }
                        }
                    }
                }
                break;
            }
        }
        
        return count;
    }
private:
    bool m_init;
    std::string m_name;
    std::string m_fullname;
    unsigned long m_baseaddr;

};
#endif
