//===-- ObjectContainerUniversalMachO.cpp -----------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "ObjectContainerUniversalMachO.h"
#include "lldb/Core/ArchSpec.h"
#include "lldb/Core/DataBuffer.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Stream.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/Target.h"

using namespace lldb;
using namespace lldb_private;
using namespace llvm::MachO;

void
ObjectContainerUniversalMachO::Initialize()
{
    PluginManager::RegisterPlugin (GetPluginNameStatic(),
                                   GetPluginDescriptionStatic(),
                                   CreateInstance,
                                   GetModuleSpecifications);
}

void
ObjectContainerUniversalMachO::Terminate()
{
    PluginManager::UnregisterPlugin (CreateInstance);
}


lldb_private::ConstString
ObjectContainerUniversalMachO::GetPluginNameStatic()
{
    static ConstString g_name("mach-o");
    return g_name;
}

const char *
ObjectContainerUniversalMachO::GetPluginDescriptionStatic()
{
    return "Universal mach-o object container reader.";
}


ObjectContainer *
ObjectContainerUniversalMachO::CreateInstance
(
    const lldb::ModuleSP &module_sp,
    DataBufferSP& data_sp,
    lldb::offset_t data_offset,
    const FileSpec *file,
    lldb::offset_t file_offset,
    lldb::offset_t length
)
{
    // We get data when we aren't trying to look for cached container information,
    // so only try and look for an architecture slice if we get data
    if (data_sp)
    {
        DataExtractor data;
        data.SetData (data_sp, data_offset, length);
        if (ObjectContainerUniversalMachO::MagicBytesMatch(data))
        {
            std::unique_ptr<ObjectContainerUniversalMachO> container_ap(new ObjectContainerUniversalMachO (module_sp, data_sp, data_offset, file, file_offset, length));
            if (container_ap->ParseHeader())
            {
                return container_ap.release();
            }
        }
    }
    return NULL;
}

bool
ObjectContainerUniversalMachO::MagicBytesMatch (const DataExtractor &data)
{
    lldb::offset_t offset = 0;
    uint32_t magic = data.GetU32(&offset);
    return magic == UniversalMagic || magic == UniversalMagicSwapped;
}

ObjectContainerUniversalMachO::ObjectContainerUniversalMachO
(
    const lldb::ModuleSP &module_sp,
    DataBufferSP& data_sp,
    lldb::offset_t data_offset,
    const FileSpec *file,
    lldb::offset_t file_offset,
    lldb::offset_t length
) :
    ObjectContainer (module_sp, file, file_offset, length, data_sp, data_offset),
    m_header(),
    m_fat_archs()
{
    memset(&m_header, 0, sizeof(m_header));
}


ObjectContainerUniversalMachO::~ObjectContainerUniversalMachO()
{
}

bool
ObjectContainerUniversalMachO::ParseHeader ()
{
    bool success = ParseHeader (m_data, m_header, m_fat_archs);
    // We no longer need any data, we parsed all we needed to parse
    // and cached it in m_header and m_fat_archs
    m_data.Clear();
    return success;
}

bool
ObjectContainerUniversalMachO::ParseHeader (lldb_private::DataExtractor &data,
                                            llvm::MachO::fat_header &header,
                                            std::vector<llvm::MachO::fat_arch> &fat_archs)
{
    bool success = false;
    // Store the file offset for this universal file as we could have a universal .o file
    // in a BSD archive, or be contained in another kind of object.
    // Universal mach-o files always have their headers in big endian.
    lldb::offset_t offset = 0;
    data.SetByteOrder (eByteOrderBig);
    header.magic = data.GetU32(&offset);
    fat_archs.clear();

    if (header.magic == UniversalMagic)
    {

        data.SetAddressByteSize(4);
        
        header.nfat_arch = data.GetU32(&offset);
        
        // Now we should have enough data for all of the fat headers, so lets index
        // them so we know how many architectures that this universal binary contains.
        uint32_t arch_idx = 0;
        for (arch_idx = 0; arch_idx < header.nfat_arch; ++arch_idx)
        {
            if (data.ValidOffsetForDataOfSize(offset, sizeof(fat_arch)))
            {
                fat_arch arch;
                if (data.GetU32(&offset, &arch, sizeof(fat_arch)/sizeof(uint32_t)))
                    fat_archs.push_back(arch);
            }
        }
        success = true;
    }
    else
    {
        memset(&header, 0, sizeof(header));
    }
    return success;
}

void
ObjectContainerUniversalMachO::Dump (Stream *s) const
{
    s->Printf("%p: ", this);
    s->Indent();
    const size_t num_archs = GetNumArchitectures();
    const size_t num_objects = GetNumObjects();
    s->Printf("ObjectContainerUniversalMachO, num_archs = %lu, num_objects = %lu", num_archs, num_objects);
    uint32_t i;
    ArchSpec arch;
    s->IndentMore();
    for (i=0; i<num_archs; i++)
    {
        s->Indent();
        GetArchitectureAtIndex(i, arch);
        s->Printf("arch[%u] = %s\n", i, arch.GetArchitectureName());
    }
    for (i=0; i<num_objects; i++)
    {
        s->Indent();
        s->Printf("object[%u] = %s\n", i, GetObjectNameAtIndex (i));
    }
    s->IndentLess();
    s->EOL();
}

size_t
ObjectContainerUniversalMachO::GetNumArchitectures () const
{
    return m_header.nfat_arch;
}

bool
ObjectContainerUniversalMachO::GetArchitectureAtIndex (uint32_t idx, ArchSpec& arch) const
{
    if (idx < m_header.nfat_arch)
    {
        arch.SetArchitecture (eArchTypeMachO, m_fat_archs[idx].cputype, m_fat_archs[idx].cpusubtype);
        return true;
    }
    return false;
}

ObjectFileSP
ObjectContainerUniversalMachO::GetObjectFile (const FileSpec *file)
{
    uint32_t arch_idx = 0;
    ArchSpec arch;
    // If the module hasn't specified an architecture yet, set it to the default 
    // architecture:
    ModuleSP module_sp (GetModule());
    if (module_sp)
    {
        if (!module_sp->GetArchitecture().IsValid())
        {
            arch = Target::GetDefaultArchitecture ();
            if (!arch.IsValid())
                arch.SetTriple (LLDB_ARCH_DEFAULT);
        }
        else
            arch = module_sp->GetArchitecture();
            
        ArchSpec curr_arch;
        // First, try to find an exact match for the Arch of the Target.
        for (arch_idx = 0; arch_idx < m_header.nfat_arch; ++arch_idx)
        {
            if (GetArchitectureAtIndex (arch_idx, curr_arch) && arch.IsExactMatch(curr_arch))
                break;
        }

        // Failing an exact match, try to find a compatible Arch of the Target.
        if (arch_idx >= m_header.nfat_arch)
        {
            for (arch_idx = 0; arch_idx < m_header.nfat_arch; ++arch_idx)
            {
                if (GetArchitectureAtIndex (arch_idx, curr_arch) && arch.IsCompatibleMatch(curr_arch))
                    break;
            }
        }

        if (arch_idx < m_header.nfat_arch)
        {
            DataBufferSP data_sp;
            lldb::offset_t data_offset = 0;
            return ObjectFile::FindPlugin (module_sp,
                                           file,
                                           m_offset + m_fat_archs[arch_idx].offset,
                                           m_fat_archs[arch_idx].size,
                                           data_sp,
                                           data_offset);
        }
    }
    return ObjectFileSP();
}


//------------------------------------------------------------------
// PluginInterface protocol
//------------------------------------------------------------------
lldb_private::ConstString
ObjectContainerUniversalMachO::GetPluginName()
{
    return GetPluginNameStatic();
}

uint32_t
ObjectContainerUniversalMachO::GetPluginVersion()
{
    return 1;
}


size_t
ObjectContainerUniversalMachO::GetModuleSpecifications (const lldb_private::FileSpec& file,
                                                        lldb::DataBufferSP& data_sp,
                                                        lldb::offset_t data_offset,
                                                        lldb::offset_t file_offset,
                                                        lldb::offset_t file_size,
                                                        lldb_private::ModuleSpecList &specs)
{
    const size_t initial_count = specs.GetSize();
    
    DataExtractor data;
    data.SetData (data_sp, data_offset, data_sp->GetByteSize());

    if (ObjectContainerUniversalMachO::MagicBytesMatch(data))
    {
        llvm::MachO::fat_header header;
        std::vector<llvm::MachO::fat_arch> fat_archs;
        if (ParseHeader (data, header, fat_archs))
        {
            for (const llvm::MachO::fat_arch &fat_arch : fat_archs)
            {
                const lldb::offset_t slice_file_offset = fat_arch.offset + file_offset;
                if (fat_arch.offset < file_size && file_size > slice_file_offset)
                {
                    ObjectFile::GetModuleSpecifications (file,
                                                         slice_file_offset,
                                                         file_size - slice_file_offset,
                                                         specs);
                }
            }
        }
    }
    return specs.GetSize() - initial_count;
}