root/src/system/runtime_loader/elf_haiku_version.cpp
/*
 * Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include "elf_haiku_version.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <image_defs.h>
#include <syscalls.h>

#include "elf_symbol_lookup.h"


// interim Haiku API versions
#define HAIKU_VERSION_PRE_GLUE_CODE             0x00000010


static bool
analyze_object_gcc_version(int fd, image_t* image, elf_ehdr& eheader,
        int32 sheaderSize, char* buffer, size_t bufferSize)
{
        if (sheaderSize <= 0)
                return false;

        if (sheaderSize > (int)bufferSize) {
                FATAL("%s: Cannot handle section headers bigger than %lu bytes\n",
                        image->path, bufferSize);
                return false;
        }

        // read section headers
        ssize_t length = _kern_read(fd, eheader.e_shoff, buffer, sheaderSize);
        if (length != sheaderSize) {
                FATAL("%s: Could not read section headers: %s\n", image->path,
                        strerror(length));
                return false;
        }

        // load the string section
        elf_shdr* sectionHeader
                = (elf_shdr*)(buffer + eheader.e_shstrndx * eheader.e_shentsize);

        if (sheaderSize + sectionHeader->sh_size > bufferSize) {
                FATAL("%s: Buffer not big enough for section string section\n",
                        image->path);
                return false;
        }

        char* sectionStrings = buffer + bufferSize - sectionHeader->sh_size;
        length = _kern_read(fd, sectionHeader->sh_offset, sectionStrings,
                sectionHeader->sh_size);
        if (length != (int)sectionHeader->sh_size) {
                FATAL("%s: Could not read section string section: %s\n", image->path,
                        strerror(length));
                return false;
        }

        // find the .comment section
        off_t commentOffset = 0;
        size_t commentSize = 0;
        for (uint32 i = 0; i < eheader.e_shnum; i++) {
                sectionHeader = (elf_shdr*)(buffer + i * eheader.e_shentsize);
                const char* sectionName = sectionStrings + sectionHeader->sh_name;
                if (sectionHeader->sh_name != 0
                        && strcmp(sectionName, ".comment") == 0) {
                        commentOffset = sectionHeader->sh_offset;
                        commentSize = sectionHeader->sh_size;
                        break;
                }
        }

        if (commentSize == 0) {
                FATAL("%s: Could not find .comment section\n", image->path);
                return false;
        }

        // read a part of the comment section
        if (commentSize > 512)
                commentSize = 512;

        length = _kern_read(fd, commentOffset, buffer, commentSize);
        if (length != (int)commentSize) {
                FATAL("%s: Could not read .comment section: %s\n", image->path,
                        strerror(length));
                return false;
        }

        // the common prefix of the strings in the .comment section
        static const char* kGCCVersionPrefix = "GCC: (";
        size_t gccVersionPrefixLen = strlen(kGCCVersionPrefix);

        size_t index = 0;
        int gccMajor = 0;
        int gccMiddle = 0;
        int gccMinor = 0;
        bool isHaiku = true;

        // Read up to 10 comments. The first three or four are usually from the
        // glue code.
        for (int i = 0; i < 10; i++) {
                // skip '\0'
                while (index < commentSize && buffer[index] == '\0')
                        index++;
                char* stringStart = buffer + index;

                // find string end
                while (index < commentSize && buffer[index] != '\0')
                        index++;

                // ignore the entry at the end of the buffer
                if (index == commentSize)
                        break;

                // We have to analyze string like these:
                // GCC: (GNU) 2.9-beos-991026
                // GCC: (GNU) 2.95.3-haiku-080322
                // GCC: (GNU) 4.1.2
                // GCC: (2016_02_29) 5.3.0
                // GCC: (2018_05_01) 7.3.0
                // GCC: (GNU) 7.3.0

                // FIXME this does not handle binaries generated with clang or other
                // compilers.

                // skip the common prefix
                if (strncmp(stringStart, kGCCVersionPrefix, gccVersionPrefixLen) != 0)
                        continue;

                // Skip the build identifier, the closing parenthesis, and the space
                // that follows it.
                // Hopefully no one is going to include nested parentheses in the
                // version string, so we can save the need for a smarter parser.
                char* gccVersion = strchr(stringStart + gccVersionPrefixLen, ')') + 2;

                // the rest is the GCC version
                char* gccPlatform = strchr(gccVersion, '-');
                char* patchLevel = NULL;
                if (gccPlatform != NULL) {
                        *gccPlatform = '\0';
                        gccPlatform++;
                        patchLevel = strchr(gccPlatform, '-');
                        if (patchLevel != NULL) {
                                *patchLevel = '\0';
                                patchLevel++;
                        }
                }

                // split the gcc version into major, middle, and minor
                int version[3] = { 0, 0, 0 };

                for (int k = 0; gccVersion != NULL && k < 3; k++) {
                        char* dot = strchr(gccVersion, '.');
                        if (dot) {
                                *dot = '\0';
                                dot++;
                        }
                        version[k] = atoi(gccVersion);
                        gccVersion = dot;
                }

                // got any version?
                if (version[0] == 0)
                        continue;

                // Select the gcc version with the smallest major, but the greatest
                // middle/minor. This should usually ignore the glue code version as
                // well as cases where e.g. in a gcc 2 program a single C file has
                // been compiled with gcc 4.
                if (gccMajor == 0 || gccMajor > version[0]
                        || (gccMajor == version[0]
                                && (gccMiddle < version[1]
                                        || (gccMiddle == version[1] && gccMinor < version[2])))) {
                        gccMajor = version[0];
                        gccMiddle = version[1];
                        gccMinor = version[2];
                }

                if (gccMajor == 2 && gccPlatform != NULL
                        && strcmp(gccPlatform, "haiku")) {
                        isHaiku = false;
                }
        }

        if (gccMajor == 0)
                return false;

        if (gccMajor == 2) {
                if (gccMiddle < 95)
                        image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
                else if (isHaiku)
                        image->abi = B_HAIKU_ABI_GCC_2_HAIKU;
                else
                        image->abi = B_HAIKU_ABI_GCC_2_BEOS;
        } else {
                if (gccMajor >= 5) {
                        // The ABI changes in libstdc++ 5+ are optional, and currently we
                        // are using it in backwards compatible mode. So, it is still
                        // generating ABI version 4.
                        gccMajor = 4;
                }
                image->abi = gccMajor << 16;
        }

        return true;
}


void
analyze_image_haiku_version_and_abi(int fd, image_t* image, elf_ehdr& eheader,
        int32 sheaderSize, char* buffer, size_t bufferSize)
{
        // Haiku API version
        elf_sym* symbol = find_symbol(image,
                SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_VERSION_VARIABLE_NAME,
                        B_SYMBOL_TYPE_DATA));
        if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
                && symbol->st_value > 0
                && symbol->Type() == STT_OBJECT
                && symbol->st_size >= sizeof(uint32)) {
                image->api_version
                        = *(uint32*)(symbol->st_value + image->regions[0].delta);
        } else
                image->api_version = 0;

        // Haiku ABI
        symbol = find_symbol(image,
                SymbolLookupInfo(B_SHARED_OBJECT_HAIKU_ABI_VARIABLE_NAME,
                        B_SYMBOL_TYPE_DATA));
        if (symbol != NULL && symbol->st_shndx != SHN_UNDEF
                && symbol->st_value > 0
                && symbol->Type() == STT_OBJECT
                && symbol->st_size >= sizeof(uint32)) {
                image->abi = *(uint32*)(symbol->st_value + image->regions[0].delta);
        } else
                image->abi = 0;

        if (image->abi == 0) {
                // No ABI version in the shared object, i.e. it has been built before
                // that was introduced in Haiku. We have to try and analyze the gcc
                // version.
                if (!analyze_object_gcc_version(fd, image, eheader, sheaderSize,
                                buffer, bufferSize)) {
                        FATAL("%s: Failed to get gcc version.\n", image->path);
                                // not really fatal, actually

                        // assume ancient BeOS
                        image->abi = B_HAIKU_ABI_GCC_2_ANCIENT;
                }
        }

        // guess the API version, if we couldn't figure it out yet
        if (image->api_version == 0) {
                image->api_version = image->abi > B_HAIKU_ABI_GCC_2_BEOS
                        ? HAIKU_VERSION_PRE_GLUE_CODE : B_HAIKU_VERSION_BEOS;
        }
}