* Copyright 2005-2009, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*
* Copyright 2002, Manuel J. Petit. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include "runtime_loader_private.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <algorithm>
#include <ByteOrder.h>
#include <directories.h>
#include <find_directory_private.h>
#include <image_defs.h>
#include <syscalls.h>
#include <user_runtime.h>
#include <vm_defs.h>
#include "elf_symbol_lookup.h"
#include "pe.h"
struct user_space_program_args *gProgramArgs;
void *__gCommPageAddress;
void *__dso_handle;
int32 __gCPUCount = 1;
const directory_which kLibraryDirectories[] = {
B_SYSTEM_LIB_DIRECTORY,
B_SYSTEM_NONPACKAGED_LIB_DIRECTORY,
B_USER_LIB_DIRECTORY,
B_USER_NONPACKAGED_LIB_DIRECTORY
};
static const char *
search_path_for_type(image_type type)
{
const char *path = NULL;
if (gProgramArgs->disable_user_addons) {
switch (type) {
case B_APP_IMAGE:
return kGlobalBinDirectory
":" kSystemAppsDirectory
":" kSystemPreferencesDirectory;
case B_LIBRARY_IMAGE:
return kAppLocalLibDirectory
":" kSystemLibDirectory;
case B_ADD_ON_IMAGE:
return kAppLocalAddonsDirectory
":" kSystemAddonsDirectory;
default:
return NULL;
}
}
switch (type) {
case B_APP_IMAGE:
path = getenv("PATH");
break;
case B_LIBRARY_IMAGE:
path = getenv("LIBRARY_PATH");
break;
case B_ADD_ON_IMAGE:
path = getenv("ADDON_PATH");
break;
default:
return NULL;
}
if (path != NULL)
return path;
switch (type) {
case B_APP_IMAGE:
return kSystemNonpackagedBinDirectory
":" kGlobalBinDirectory
":" kSystemAppsDirectory
":" kSystemPreferencesDirectory;
case B_LIBRARY_IMAGE:
return kAppLocalLibDirectory
":" kSystemNonpackagedLibDirectory
":" kSystemLibDirectory;
case B_ADD_ON_IMAGE:
return kAppLocalAddonsDirectory
":" kSystemNonpackagedAddonsDirectory
":" kSystemAddonsDirectory;
default:
return NULL;
}
}
static bool
replace_executable_path_placeholder(const char*& dir, int& dirLength,
const char* placeholder, size_t placeholderLength,
const char* replacementSubPath, char*& buffer, size_t& bufferSize,
status_t& _error)
{
if (dirLength < (int)placeholderLength
|| strncmp(dir, placeholder, placeholderLength) != 0) {
return false;
}
if (replacementSubPath == NULL) {
_error = B_ENTRY_NOT_FOUND;
return true;
}
char* lastSlash = strrchr(replacementSubPath, '/');
size_t toCopy;
if (lastSlash != NULL) {
toCopy = lastSlash - replacementSubPath;
strlcpy(buffer, replacementSubPath,
std::min((ssize_t)bufferSize, lastSlash + 1 - replacementSubPath));
} else {
replacementSubPath = ".";
toCopy = 1;
strlcpy(buffer, ".", bufferSize);
}
if (toCopy >= bufferSize) {
_error = B_NAME_TOO_LONG;
return true;
}
memcpy(buffer, replacementSubPath, toCopy);
buffer[toCopy] = '\0';
buffer += toCopy;
bufferSize -= toCopy;
dir += placeholderLength;
dirLength -= placeholderLength;
_error = B_OK;
return true;
}
static int
try_open_executable(const char *dir, int dirLength, const char *name,
const char *programPath, const char *requestingObjectPath,
const char *abiSpecificSubDir, char *path, size_t pathLength)
{
size_t nameLength = strlen(name);
struct stat stat;
status_t status;
if (dirLength > 0) {
char *buffer = path;
size_t subDirLen = 0;
if (programPath == NULL)
programPath = gProgramArgs->program_path;
if (replace_executable_path_placeholder(dir, dirLength, "%A", 2,
programPath, buffer, pathLength, status)
|| replace_executable_path_placeholder(dir, dirLength, "$ORIGIN", 7,
requestingObjectPath, buffer, pathLength, status)) {
if (status != B_OK)
return status;
} else if (abiSpecificSubDir != NULL) {
for (int i = 0; i < 4; ++i) {
char buffer[PATH_MAX];
status_t result = __find_directory(kLibraryDirectories[i], -1,
false, buffer, PATH_MAX);
if (result == B_OK && strncmp(dir, buffer, dirLength) == 0) {
subDirLen = strlen(abiSpecificSubDir) + 1;
break;
}
}
}
if (dirLength + 1 + subDirLen + nameLength >= pathLength)
return B_NAME_TOO_LONG;
memcpy(buffer, dir, dirLength);
buffer[dirLength] = '/';
if (subDirLen > 0) {
memcpy(buffer + dirLength + 1, abiSpecificSubDir, subDirLen - 1);
buffer[dirLength + subDirLen] = '/';
}
strcpy(buffer + dirLength + 1 + subDirLen, name);
} else {
if (nameLength >= pathLength)
return B_NAME_TOO_LONG;
strcpy(path + dirLength + 1, name);
}
TRACE(("runtime_loader: try_open_container(): %s\n", path));
status = _kern_read_stat(AT_FDCWD, path, false, &stat, sizeof(struct stat));
if (status < B_OK)
return status;
if (S_ISLNK(stat.st_mode)) {
char buffer[PATH_MAX];
size_t length = PATH_MAX - 1;
char *lastSlash;
status = _kern_read_link(AT_FDCWD, path, buffer, &length);
if (status < B_OK)
return status;
buffer[length] = '\0';
lastSlash = strrchr(path, '/');
if (buffer[0] != '/' && lastSlash != NULL) {
strlcpy(lastSlash + 1, buffer, lastSlash + 1 - path + pathLength);
} else
strlcpy(path, buffer, pathLength);
}
return _kern_open(AT_FDCWD, path, O_RDONLY, 0);
}
static int
search_executable_in_path_list(const char *name, const char *pathList,
int pathListLen, const char *programPath, const char *requestingObjectPath,
const char *abiSpecificSubDir, char *pathBuffer, size_t pathBufferLength)
{
const char *pathListEnd = pathList + pathListLen;
status_t status = B_ENTRY_NOT_FOUND;
TRACE(("runtime_loader: search_container_in_path_list() %s in %.*s\n", name,
pathListLen, pathList));
while (pathListLen > 0) {
const char *pathEnd = pathList;
int fd;
while (pathEnd < pathListEnd && *pathEnd != ':')
pathEnd++;
fd = try_open_executable(pathList, pathEnd - pathList, name,
programPath, requestingObjectPath, abiSpecificSubDir, pathBuffer,
pathBufferLength);
if (fd >= 0) {
struct stat stat;
status = _kern_read_stat(fd, NULL, true, &stat, sizeof(struct stat));
if (status == B_OK) {
if (!S_ISDIR(stat.st_mode))
return fd;
status = B_IS_A_DIRECTORY;
}
_kern_close(fd);
}
pathListLen = pathListEnd - pathEnd - 1;
pathList = pathEnd + 1;
}
return status;
}
int
open_executable(char *name, image_type type, const char *rpath, const char* runpath,
const char *programPath, const char *requestingObjectPath,
const char *abiSpecificSubDir)
{
char buffer[PATH_MAX];
int fd = B_ENTRY_NOT_FOUND;
if (strchr(name, '/')) {
fd = _kern_open(AT_FDCWD, name, O_RDONLY, 0);
if (fd >= 0 || type == B_APP_IMAGE)
return fd;
if (type == B_ADD_ON_IMAGE && name[0] == '/')
return fd;
if (type == B_LIBRARY_IMAGE) {
const char* paths = strrchr(name, '/') + 1;
memmove(name, paths, strlen(paths) + 1);
}
}
const char* pathString = runpath;
if (pathString == NULL)
pathString = rpath;
if (pathString != NULL) {
const char *semicolon = strchr(pathString, ';');
const char *firstList = (semicolon ? pathString : NULL);
const char *secondList = (semicolon ? semicolon + 1 : pathString);
if (firstList) {
fd = search_executable_in_path_list(name, firstList,
semicolon - firstList, programPath, requestingObjectPath, NULL,
buffer, sizeof(buffer));
}
if (fd < 0) {
fd = search_executable_in_path_list(name, secondList,
strlen(secondList), programPath, requestingObjectPath, NULL,
buffer, sizeof(buffer));
}
}
if (fd < 0) {
if (const char *paths = search_path_for_type(type)) {
fd = search_executable_in_path_list(name, paths, strlen(paths),
programPath, NULL, abiSpecificSubDir, buffer, sizeof(buffer));
}
}
if (fd >= 0) {
TRACE(("runtime_loader: open_executable(%s): found at %s\n", name, buffer));
strlcpy(name, buffer, PATH_MAX);
}
return fd;
}
Applies haiku-specific fixes to a shebang line.
*/
static void
fixup_shebang(char *invoker)
{
while (*invoker == ' ' || *invoker == '\t')
++invoker;
if (memcmp(invoker, "/usr/bin/", strlen("/usr/bin/")) == 0)
memmove(invoker, invoker + 4, strlen(invoker + 4) + 1);
}
Tests if there is an executable file at the provided path. It will
also test if the file has a valid ELF header or is a shell script.
Even if the runtime loader does not need to be able to deal with
both types, the caller will give scripts a proper treatment.
*/
status_t
test_executable(const char *name, char *invoker)
{
char path[B_PATH_NAME_LENGTH];
char buffer[B_FILE_NAME_LENGTH];
status_t status;
ssize_t length;
int fd;
if (name == NULL)
return B_BAD_VALUE;
strlcpy(path, name, sizeof(path));
fd = open_executable(path, B_APP_IMAGE, NULL, NULL, NULL, NULL, NULL);
if (fd < B_OK)
return fd;
status = _kern_access(AT_FDCWD, path, X_OK, false);
if (status != B_OK)
goto out;
length = _kern_read(fd, 0, buffer, sizeof(buffer));
if (length < 0) {
status = length;
goto out;
}
status = elf_verify_header(buffer, length);
#ifdef _COMPAT_MODE
#ifdef __x86_64__
if (status == B_NOT_AN_EXECUTABLE)
status = elf32_verify_header(buffer, length);
#else
if (status == B_NOT_AN_EXECUTABLE)
status = elf64_verify_header(buffer, length);
#endif
#endif
if (status == B_NOT_AN_EXECUTABLE) {
if (!strncmp(buffer, "#!", 2)) {
char *end;
buffer[min_c((size_t)length, sizeof(buffer) - 1)] = '\0';
end = strchr(buffer, '\n');
if (end == NULL) {
status = E2BIG;
goto out;
} else
end[0] = '\0';
if (invoker) {
strcpy(invoker, buffer + 2);
fixup_shebang(invoker);
}
status = B_OK;
} else {
status = pe_verify_header(buffer, length);
if (status == B_OK)
status = B_UNKNOWN_EXECUTABLE;
}
} else if (status == B_OK) {
elf_ehdr *elfHeader = (elf_ehdr *)buffer;
if (elfHeader->e_entry == 0) {
status = B_NOT_AN_EXECUTABLE;
} else if (invoker)
invoker[0] = '\0';
}
out:
_kern_close(fd);
return status;
}
static bool
determine_x86_abi(int fd, const Elf32_Ehdr& elfHeader, bool& _isGcc2)
{
#if B_HOST_IS_LENDIAN
struct stat st;
if (_kern_read_stat(fd, NULL, true, &st, sizeof(st)) != B_OK)
return false;
void* fileBaseAddress;
area_id area = _kern_map_file("mapped file", &fileBaseAddress,
B_ANY_ADDRESS, st.st_size, B_READ_AREA, REGION_NO_PRIVATE_MAP, false,
fd, 0);
if (area < 0)
return false;
struct AreaDeleter {
AreaDeleter(area_id area)
:
fArea(area)
{
}
~AreaDeleter()
{
_kern_delete_area(fArea);
}
private:
area_id fArea;
} areaDeleter(area);
if (elfHeader.e_shoff == 0 || elfHeader.e_shentsize < sizeof(Elf32_Shdr))
return false;
size_t sectionHeadersSize = elfHeader.e_shentsize * elfHeader.e_shnum;
if (elfHeader.e_shoff + (off_t)sectionHeadersSize > st.st_size)
return false;
void* sectionHeaders = (uint8*)fileBaseAddress + elfHeader.e_shoff;
uint32* symbolHash = NULL;
uint32 symbolHashSize = 0;
uint32 symbolHashChainSize = 0;
Elf32_Sym* symbolTable = NULL;
uint32 symbolTableSize = 0;
const char* stringTable = NULL;
off_t stringTableSize = 0;
for (int32 i = 0; i < elfHeader.e_shnum; i++) {
Elf32_Shdr* sectionHeader
= (Elf32_Shdr*)((uint8*)sectionHeaders + i * elfHeader.e_shentsize);
if ((off_t)sectionHeader->sh_offset + (off_t)sectionHeader->sh_size
> st.st_size) {
continue;
}
void* sectionAddress = (uint8*)fileBaseAddress
+ sectionHeader->sh_offset;
switch (sectionHeader->sh_type) {
case SHT_HASH:
symbolHash = (uint32*)sectionAddress;
if (sectionHeader->sh_size < (off_t)sizeof(symbolHash[0]))
return false;
symbolHashSize = symbolHash[0];
symbolHashChainSize
= sectionHeader->sh_size / sizeof(symbolHash[0]);
if (symbolHashChainSize < symbolHashSize + 2)
return false;
symbolHashChainSize -= symbolHashSize + 2;
break;
case SHT_DYNSYM:
symbolTable = (Elf32_Sym*)sectionAddress;
symbolTableSize = sectionHeader->sh_size;
break;
case SHT_STRTAB:
if (sectionHeader->sh_addr == 0)
continue;
stringTable = (const char*)sectionAddress;
stringTableSize = (off_t)sectionHeader->sh_size;
break;
default:
continue;
}
}
if (symbolHash == NULL || symbolTable == NULL || stringTable == NULL)
return false;
uint32 symbolCount
= std::min(symbolTableSize / (uint32)sizeof(Elf32_Sym),
symbolHashChainSize);
if (symbolCount < symbolHashSize)
return false;
const char* name = B_SHARED_OBJECT_HAIKU_ABI_VARIABLE_NAME;
size_t nameLength = strlen(name);
uint32 bucket = elf_hash(name) % symbolHashSize;
for (uint32 i = symbolHash[bucket + 2]; i < symbolCount && i != STN_UNDEF;
i = symbolHash[2 + symbolHashSize + i]) {
Elf32_Sym* symbol = symbolTable + i;
if (symbol->st_shndx != SHN_UNDEF
&& ((symbol->Bind() == STB_GLOBAL) || (symbol->Bind() == STB_WEAK))
&& symbol->Type() == STT_OBJECT
&& (off_t)symbol->st_name + (off_t)nameLength < stringTableSize
&& strcmp(stringTable + symbol->st_name, name) == 0) {
if (symbol->st_value > 0 && symbol->st_size >= sizeof(uint32)
&& symbol->st_shndx < elfHeader.e_shnum) {
Elf32_Shdr* sectionHeader = (Elf32_Shdr*)((uint8*)sectionHeaders
+ symbol->st_shndx * elfHeader.e_shentsize);
if (symbol->st_value >= sectionHeader->sh_addr
&& symbol->st_value
<= sectionHeader->sh_addr + sectionHeader->sh_size) {
off_t fileOffset = symbol->st_value - sectionHeader->sh_addr
+ sectionHeader->sh_offset;
if (fileOffset + (off_t)sizeof(uint32) <= st.st_size) {
uint32 abi
= *(uint32*)((uint8*)fileBaseAddress + fileOffset);
_isGcc2 = (abi & B_HAIKU_ABI_MAJOR)
== B_HAIKU_ABI_GCC_2;
return true;
}
}
}
return false;
}
}
_isGcc2 = true;
return true;
#else
return false;
#endif
}
static status_t
get_executable_architecture(int fd, const char** _architecture)
{
Elf32_Ehdr elfHeader;
ssize_t bytesRead = _kern_read(fd, 0, &elfHeader, sizeof(elfHeader));
if (bytesRead < 0)
return bytesRead;
if ((size_t)bytesRead != sizeof(elfHeader))
return B_NOT_AN_EXECUTABLE;
if (memcmp(elfHeader.e_ident, ELFMAG, 4) != 0)
return B_NOT_AN_EXECUTABLE;
uint16 machine = elfHeader.e_machine;
if ((elfHeader.e_ident[EI_DATA] == ELFDATA2LSB) != (B_HOST_IS_LENDIAN != 0))
machine = (machine >> 8) | (machine << 8);
const char* architecture = NULL;
switch (machine) {
case EM_386:
case EM_486:
{
bool isGcc2;
if (determine_x86_abi(fd, elfHeader, isGcc2) && isGcc2)
architecture = "x86_gcc2";
else
architecture = "x86";
break;
}
case EM_68K:
architecture = "m68k";
break;
case EM_PPC:
architecture = "ppc";
break;
case EM_ARM:
architecture = "arm";
break;
case EM_ARM64:
architecture = "arm64";
break;
case EM_X86_64:
architecture = "x86_64";
break;
case EM_RISCV:
architecture = "riscv";
break;
}
if (architecture == NULL)
return B_NOT_SUPPORTED;
*_architecture = architecture;
return B_OK;
}
status_t
get_executable_architecture(const char* path, const char** _architecture)
{
int fd = _kern_open(AT_FDCWD, path, O_RDONLY, 0);
if (fd < 0)
return fd;
status_t error = get_executable_architecture(fd, _architecture);
_kern_close(fd);
return error;
}
This is the main entry point of the runtime loader as
specified by its ld-script.
*/
int
runtime_loader(void* _args, void* commpage)
{
void *entry = NULL;
int returnCode;
gProgramArgs = (struct user_space_program_args *)_args;
__gCommPageAddress = commpage;
{
int32 i;
addr_t relocationOffset = 0;
if (gProgramArgs->arg_count > 0)
relocationOffset = (addr_t)gProgramArgs->args[0];
else if (gProgramArgs->env_count > 0)
relocationOffset = (addr_t)gProgramArgs->env[0];
relocationOffset = (addr_t)gProgramArgs->args - relocationOffset
+ (gProgramArgs->arg_count + gProgramArgs->env_count + 2)
* sizeof(char*);
for (i = 0; i < gProgramArgs->arg_count; i++)
gProgramArgs->args[i] += relocationOffset;
for (i = 0; i < gProgramArgs->env_count; i++)
gProgramArgs->env[i] += relocationOffset;
}
#if DEBUG_RLD
close(0); open("/dev/console", 0);
close(1); open("/dev/console", 0);
close(2); open("/dev/console", 0);
#endif
if (heap_init() < B_OK)
return 1;
rldexport_init();
rldelf_init();
load_program(gProgramArgs->program_path, &entry);
if (entry == NULL)
return -1;
returnCode = ((int (*)(int, void *, void *))entry)(gProgramArgs->arg_count,
gProgramArgs->args, gProgramArgs->env);
terminate_program();
return returnCode;
}