/* * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de. * Copyright 2003-2011, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. * * Copyright 2002, Manuel J. Petit. All rights reserved. * Copyright 2001, Travis Geiselbrecht. All rights reserved. * Distributed under the terms of the NewOS License. */ #include "images.h" #include #include #include #include #include #include #include #include "add_ons.h" #include "elf_tls.h" #include "runtime_loader_private.h" #include // keep in sync with app ldscript #ifdef __x86_64__ // runtime_loader potentially occupies 0x200000 - 0x600000 due to large // page segment alignment. # define RLD_PROGRAM_BASE 0x600000 # define MAX_PAGE_SIZE 0x200000 #elif __arm__ # define RLD_PROGRAM_BASE 0x200000 # define MAX_PAGE_SIZE 0x10000 #else # define RLD_PROGRAM_BASE 0x200000 # define MAX_PAGE_SIZE B_PAGE_SIZE #endif bool gInvalidImageIDs; static image_queue_t sLoadedImages = {0, 0}; static image_queue_t sDisposableImages = {0, 0}; static uint32 sLoadedImageCount = 0; //! Remaps the image ID of \a image after fork. static status_t update_image_id(image_t* image) { int32 cookie = 0; image_info info; while (_kern_get_next_image_info(B_CURRENT_TEAM, &cookie, &info, sizeof(image_info)) == B_OK) { for (uint32 i = 0; i < image->num_regions; i++) { if (image->regions[i].vmstart == (addr_t)info.text) { image->id = info.id; return B_OK; } } } FATAL("Could not update image ID %" B_PRId32 " after fork()!\n", image->id); return B_ENTRY_NOT_FOUND; } static void enqueue_image(image_queue_t* queue, image_t* image) { image->next = NULL; image->prev = queue->tail; if (queue->tail) queue->tail->next = image; queue->tail = image; if (!queue->head) queue->head = image; } static void dequeue_image(image_queue_t* queue, image_t* image) { if (image->next) image->next->prev = image->prev; else queue->tail = image->prev; if (image->prev) image->prev->next = image->next; else queue->head = image->next; image->prev = NULL; image->next = NULL; } static image_t* find_image_in_queue(image_queue_t* queue, const char* name, bool isPath, uint32 typeMask) { for (image_t* image = queue->head; image; image = image->next) { const char* imageName = isPath ? image->path : image->name; int length = isPath ? sizeof(image->path) : sizeof(image->name); if (!strncmp(imageName, name, length) && (typeMask & IMAGE_TYPE_TO_MASK(image->type)) != 0) { return image; } } return NULL; } static void update_image_flags_recursively(image_t* image, uint32 flagsToSet, uint32 flagsToClear) { image_t* queue[sLoadedImageCount]; uint32 count = 0; uint32 index = 0; queue[count++] = image; image->flags |= RFLAG_VISITED; while (index < count) { // pop next image image = queue[index++]; // push dependencies for (uint32 i = 0; i < image->num_needed; i++) { image_t* needed = image->needed[i]; if ((needed->flags & RFLAG_VISITED) == 0) { queue[count++] = needed; needed->flags |= RFLAG_VISITED; } } } // update flags for (uint32 i = 0; i < count; i++) { queue[i]->flags = (queue[i]->flags | flagsToSet) & ~(flagsToClear | RFLAG_VISITED); } } static uint32 topological_sort(image_t* image, uint32 slot, image_t** initList, uint32 sortFlag) { if (image->flags & sortFlag) return slot; image->flags |= sortFlag; /* make sure we don't visit this one */ for (uint32 i = 0; i < image->num_needed; i++) slot = topological_sort(image->needed[i], slot, initList, sortFlag); initList[slot] = image; return slot + 1; } /*! Finds the load address and address specifier of the given image region. */ static void get_image_region_load_address(image_t* image, uint32 index, long lastDelta, bool fixed, addr_t& loadAddress, uint32& addressSpecifier) { if (!fixed) { // relocatable image... we can afford to place wherever if (index == 0) { // but only the first segment gets a free ride loadAddress = RLD_PROGRAM_BASE; addressSpecifier = B_RANDOMIZED_BASE_ADDRESS; } else { loadAddress = image->regions[index].vmstart + lastDelta; addressSpecifier = B_EXACT_ADDRESS; } } else { // not relocatable, put it where it asks or die trying loadAddress = image->regions[index].vmstart; addressSpecifier = B_EXACT_ADDRESS; } } // #pragma mark - image_t* create_image(const char* name, const char* path, int regionCount) { size_t allocSize = sizeof(image_t) + (regionCount - 1) * sizeof(elf_region_t); image_t* image = (image_t*)malloc(allocSize); if (image == NULL) { FATAL("no memory for image %s\n", path); return NULL; } memset(image, 0, allocSize); strlcpy(image->path, path, sizeof(image->path)); // Make the last component of the supplied name the image name. // If present, DT_SONAME will replace this name. const char* lastSlash = strrchr(name, '/'); if (lastSlash != NULL) strlcpy(image->name, lastSlash + 1, sizeof(image->name)); else strlcpy(image->name, name, sizeof(image->name)); image->ref_count = 1; image->num_regions = regionCount; return image; } void delete_image_struct(image_t* image) { #ifdef DEBUG size_t size = sizeof(image_t) + (image->num_regions - 1) * sizeof(elf_region_t); memset(image->needed, 0xa5, sizeof(image->needed[0]) * image->num_needed); #endif free(image->needed); free(image->versions); while (RuntimeLoaderSymbolPatcher* patcher = image->defined_symbol_patchers) { image->defined_symbol_patchers = patcher->next; delete patcher; } while (RuntimeLoaderSymbolPatcher* patcher = image->undefined_symbol_patchers) { image->undefined_symbol_patchers = patcher->next; delete patcher; } #ifdef DEBUG // overwrite images to make sure they aren't accidently reused anywhere memset(image, 0xa5, size); #endif free(image); } void delete_image(image_t* image) { if (image == NULL) return; _kern_unregister_image(image->id); // registered in load_container() delete_image_struct(image); } void put_image(image_t* image) { // If all references to the image are gone, add it to the disposable list // and remove all dependencies if (atomic_add(&image->ref_count, -1) == 1) { size_t i; dequeue_image(&sLoadedImages, image); enqueue_image(&sDisposableImages, image); sLoadedImageCount--; for (i = 0; i < image->num_needed; i++) put_image(image->needed[i]); } } status_t map_image(int fd, char const* path, image_t* image, bool fixed) { // cut the file name from the path as base name for the created areas const char* baseName = strrchr(path, '/'); if (baseName != NULL) baseName++; else baseName = path; // determine how much space we need for all loaded segments addr_t reservedAddress = 0; addr_t loadAddress; size_t reservedSize = 0; size_t length = 0; uint32 addressSpecifier = B_RANDOMIZED_ANY_ADDRESS; for (uint32 i = 0; i < image->num_regions; i++) { // for BeOS compatibility: if we load an old BeOS executable, we // have to relocate it, if possible - we recognize it because the // vmstart is set to 0 (hopefully always) if (fixed && image->regions[i].vmstart == 0) fixed = false; uint32 regionAddressSpecifier; get_image_region_load_address(image, i, i > 0 ? loadAddress - image->regions[i - 1].vmstart : 0, fixed, loadAddress, regionAddressSpecifier); if (i == 0) { reservedAddress = loadAddress; addressSpecifier = regionAddressSpecifier; } length += TO_PAGE_SIZE(image->regions[i].vmsize + (loadAddress % B_PAGE_SIZE)); size_t size = TO_PAGE_SIZE(loadAddress + image->regions[i].vmsize) - reservedAddress; if (size > reservedSize) reservedSize = size; } // Check whether the segments have an unreasonable amount of unused space // inbetween. if (reservedSize > length + MAX_PAGE_SIZE * 2) return B_BAD_DATA; // reserve that space and allocate the areas from that one if (_kern_reserve_address_range(&reservedAddress, addressSpecifier, reservedSize) != B_OK) return B_NO_MEMORY; for (uint32 i = 0; i < image->num_regions; i++) { char regionName[B_OS_NAME_LENGTH]; snprintf(regionName, sizeof(regionName), "%s_seg%" B_PRIu32 "%s", baseName, i, (image->regions[i].flags & RFLAG_RW) ? "rw" : "ro"); get_image_region_load_address(image, i, i > 0 ? image->regions[i - 1].delta : 0, fixed, loadAddress, addressSpecifier); // If the image position is arbitrary, we must let it point to the start // of the reserved address range. if (addressSpecifier != B_EXACT_ADDRESS) loadAddress = reservedAddress; if ((image->regions[i].flags & RFLAG_ANON) != 0) { image->regions[i].id = _kern_create_area(regionName, (void**)&loadAddress, B_EXACT_ADDRESS, image->regions[i].vmsize, B_NO_LOCK, B_READ_AREA | B_WRITE_AREA); if (image->regions[i].id < 0) { _kern_unreserve_address_range(reservedAddress, reservedSize); return image->regions[i].id; } } else { // Map all segments r/w first -- write access might be needed for // relocations. When we've done with those we change the protection // of read-only segments back to read-only. We map those segments // over-committing, since quite likely only a relatively small // number of pages needs to be touched and we want to avoid a lot // of memory to be committed for them temporarily, just because we // have to write map them. uint32 protection = B_READ_AREA | B_WRITE_AREA | ((image->regions[i].flags & RFLAG_RW) != 0 ? 0 : B_OVERCOMMITTING_AREA); image->regions[i].id = _kern_map_file(regionName, (void**)&loadAddress, B_EXACT_ADDRESS, image->regions[i].vmsize, protection, REGION_PRIVATE_MAP, false, fd, PAGE_BASE(image->regions[i].fdstart)); if (image->regions[i].id < 0) { _kern_unreserve_address_range(reservedAddress, reservedSize); return image->regions[i].id; } TRACE(("\"%s\" at %p, 0x%lx bytes (%s)\n", path, (void *)loadAddress, image->regions[i].vmsize, image->regions[i].flags & RFLAG_RW ? "rw" : "read-only")); // handle trailer bits in data segment if (image->regions[i].flags & RFLAG_RW) { addr_t startClearing = loadAddress + PAGE_OFFSET(image->regions[i].start) + image->regions[i].size; addr_t toClear = image->regions[i].vmsize - PAGE_OFFSET(image->regions[i].start) - image->regions[i].size; TRACE(("cleared 0x%lx and the following 0x%lx bytes\n", startClearing, toClear)); memset((void *)startClearing, 0, toClear); } } image->regions[i].delta = loadAddress - image->regions[i].vmstart; image->regions[i].vmstart = loadAddress; if (i == 0) { TLSBlockTemplates::Get().SetBaseAddress(image->dso_tls_id, loadAddress); } } if (image->dynamic_ptr != 0) image->dynamic_ptr += image->regions[0].delta; return B_OK; } void unmap_image(image_t* image) { for (uint32 i = 0; i < image->num_regions; i++) { _kern_delete_area(image->regions[i].id); image->regions[i].id = -1; } } /*! This function will change the protection of all read-only segments to really be read-only (and executable). The areas have to be read/write first, so that they can be relocated. If at least one image is in compatibility mode then we allow execution of all areas. */ void remap_images() { for (image_t* image = sLoadedImages.head; image != NULL; image = image->next) { for (uint32 i = 0; i < image->num_regions; i++) { // we only need to do this once, so we remember those we've already // mapped if ((image->regions[i].flags & RFLAG_REMAPPED) != 0) continue; status_t result = B_OK; if ((image->regions[i].flags & RFLAG_RW) == 0) { result = _kern_set_area_protection(image->regions[i].id, B_READ_AREA | B_EXECUTE_AREA); } else if (image->abi < B_HAIKU_ABI_GCC_2_HAIKU) { result = _kern_set_area_protection(image->regions[i].id, B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA); } if (result == B_OK) image->regions[i].flags |= RFLAG_REMAPPED; } } } void register_image(image_t* image, int fd, const char* path) { struct stat stat; extended_image_info info; // TODO: set these correctly info.basic_info.id = 0; info.basic_info.type = image->type; info.basic_info.sequence = 0; info.basic_info.init_order = 0; info.basic_info.init_routine = (void (*)())image->init_routine; info.basic_info.term_routine = (void (*)())image->term_routine; if (_kern_read_stat(fd, NULL, false, &stat, sizeof(struct stat)) == B_OK) { info.basic_info.device = stat.st_dev; info.basic_info.node = stat.st_ino; } else { info.basic_info.device = -1; info.basic_info.node = -1; } // We may have split segments into separate regions. Compute the correct // segments for the image info. addr_t textBase = 0; addr_t textEnd = 0; addr_t dataBase = 0; addr_t dataEnd = 0; for (uint32 i= 0; i < image->num_regions; i++) { addr_t base = image->regions[i].vmstart; addr_t end = base + image->regions[i].vmsize; if (image->regions[i].flags & RFLAG_RW) { // data if (dataBase == 0) { dataBase = base; dataEnd = end; } else { dataBase = std::min(dataBase, base); dataEnd = std::max(dataEnd, end); } } else { // text if (textBase == 0) { textBase = base; textEnd = end; } else { textBase = std::min(textBase, base); textEnd = std::max(textEnd, end); } } } strlcpy(info.basic_info.name, path, sizeof(info.basic_info.name)); info.basic_info.text = (void*)textBase; info.basic_info.text_size = textEnd - textBase; info.basic_info.data = (void*)dataBase; info.basic_info.data_size = dataEnd - dataBase; info.basic_info.api_version = image->api_version; info.basic_info.abi = image->abi; info.text_delta = image->regions[0].delta; info.symbol_table = image->syms; info.symbol_hash = image->symhash; info.string_table = image->strtab; image->id = _kern_register_image(&info, sizeof(info)); } //! After fork, we lazily rebuild the image IDs of all loaded images. status_t update_image_ids() { for (image_t* image = sLoadedImages.head; image; image = image->next) { status_t status = update_image_id(image); if (status != B_OK) return status; } for (image_t* image = sDisposableImages.head; image; image = image->next) { status_t status = update_image_id(image); if (status != B_OK) return status; } gInvalidImageIDs = false; return B_OK; } image_queue_t& get_loaded_images() { return sLoadedImages; } image_queue_t& get_disposable_images() { return sDisposableImages; } uint32 count_loaded_images() { return sLoadedImageCount; } void enqueue_loaded_image(image_t* image) { enqueue_image(&sLoadedImages, image); sLoadedImageCount++; } void dequeue_loaded_image(image_t* image) { dequeue_image(&sLoadedImages, image); sLoadedImageCount--; } void dequeue_disposable_image(image_t* image) { dequeue_image(&sDisposableImages, image); } image_t* find_loaded_image_by_name(char const* name, uint32 typeMask) { bool isPath = strchr(name, '/') != NULL; return find_image_in_queue(&sLoadedImages, name, isPath, typeMask); } image_t* find_loaded_image_by_id(image_id id, bool ignoreDisposable) { if (gInvalidImageIDs) { // After fork, we lazily rebuild the image IDs of all loaded images update_image_ids(); } for (image_t* image = sLoadedImages.head; image; image = image->next) { if (image->id == id) return image; } if (ignoreDisposable) return NULL; for (image_t* image = sDisposableImages.head; image; image = image->next) { if (image->id == id) return image; } return NULL; } image_t* find_loaded_image_by_address(addr_t address) { for (image_t* image = sLoadedImages.head; image; image = image->next) { for (uint32 i = 0; i < image->num_regions; i++) { elf_region_t& region = image->regions[i]; if (region.vmstart <= address && region.vmstart - 1 + region.vmsize >= address) return image; } } return NULL; } void set_image_flags_recursively(image_t* image, uint32 flags) { update_image_flags_recursively(image, flags, 0); } void clear_image_flags_recursively(image_t* image, uint32 flags) { update_image_flags_recursively(image, 0, flags); } /*! Returns a topologically sorted image list. If \a image is non-NULL, an array containing the image and all its transitive dependencies is returned. If \a image is NULL, all loaded images are returned. In either case dependencies are listed before images depending on them. \param image The image specifying the tree of images that shall be sorted. If NULL, all loaded images are sorted. \param _list On success it will be set to an array of the sorted images. The caller is responsible for free()ing it. \param sortFlags The image flag that shall be used for sorting. Images that already have this flag set are ignored (and their dependencies, unless they are also reachable via another path). The flag will be set on all returned images. \return The number of images in the returned array or an error code on failure. */ ssize_t get_sorted_image_list(image_t* image, image_t*** _list, uint32 sortFlag) { image_t** list; list = (image_t**)malloc(sLoadedImageCount * sizeof(image_t*)); if (list == NULL) { FATAL("memory shortage in get_sorted_image_list()"); *_list = NULL; return B_NO_MEMORY; } memset(list, 0, sLoadedImageCount * sizeof(image_t*)); *_list = list; if (image != NULL) return topological_sort(image, 0, list, sortFlag); // no image given -- sort all loaded images uint32 count = 0; image = sLoadedImages.head; while (image != NULL) { count = topological_sort(image, count, list, sortFlag); image = image->next; } return count; }