3DS: Implement dynamic plugins

Allows a full build to run on old generation devices
This commit is contained in:
Bastien Bouclet 2019-12-01 17:19:50 +01:00
parent a51c23abd3
commit 34e835a20c
13 changed files with 430 additions and 36 deletions

View File

@ -260,6 +260,11 @@ MODULE_OBJS += \
timer/tizen/timer.o
endif
ifeq ($(BACKEND),3ds)
MODULE_OBJS += \
plugins/3ds/3ds-provider.o
endif
ifeq ($(BACKEND),ds)
MODULE_OBJS += \
fs/ds/ds-fs.o \

View File

@ -28,7 +28,7 @@ clean_3ds:
$(RM) -rf romfs
$(RM) -rf dist_3ds
romfs: $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_3DS_EXTRA_FILES)
romfs: $(DIST_FILES_THEMES) $(DIST_FILES_ENGINEDATA) $(DIST_FILES_NETWORKING) $(DIST_FILES_VKEYBD) $(DIST_3DS_EXTRA_FILES) $(PLUGINS)
@rm -rf romfs
@mkdir -p romfs
@cp $(DIST_FILES_THEMES) romfs/
@ -44,6 +44,10 @@ endif
ifdef DIST_3DS_EXTRA_FILES
@cp -a $(DIST_3DS_EXTRA_FILES) romfs/
endif
ifeq ($(DYNAMIC_MODULES),1)
@mkdir -p romfs/plugins
@for i in $(PLUGINS); do $(STRIP) --strip-debug $$i -o romfs/plugins/`basename $$i`; done
endif
$(TARGET).smdh: $(APP_ICON)
@smdhtool --create "$(APP_TITLE)" "$(APP_DESCRIPTION)" "$(APP_AUTHOR)" $(APP_ICON) $@

View File

@ -20,7 +20,6 @@ Table of Contents:
* * [4.1.1 Compiling third-party libraries](#411-compiling-third-party-libraries)
* * [4.1.2 Manually setting up the environment](#412-manually-setting-up-the-environment)
* [4.2 Compiling ScummVM](#42-compiling-scummvm)
* [4.3 Warning for build sizes](#43-warning-for-build-sizes)
1.0) Installation
-----------------
@ -222,7 +221,7 @@ The name of the file must be `cacert.pem`.
From the root of the scummvm repository:
```
$ ./configure --host=3ds
$ ./configure --host=3ds --enable-plugins --default-dynamic
$ make
```
Additionally compile to specific formats to be used on the 3DS:
@ -230,7 +229,6 @@ Additionally compile to specific formats to be used on the 3DS:
$ make scummvm.3dsx
$ make scummvm.cia
```
**_Read the warning about build sizes below._**
Assuming everything was successful, you'll be able to find the binary
files in the root of your scummvm folder.
@ -238,14 +236,5 @@ files in the root of your scummvm folder.
Note: for the CIA format, you will need the 'makerom' and 'bannertool' tools which are
not supplied with devkitPro.
4.3) Warning for build sizes
---------------------------
The above configuration command will include all game engines by default and will
likely be too massive to be stable using either the 3DSX or the CIA format.
Until dynamic modules are figured out, you should configure engines like this:
```
$ ./configure --host=3ds --disable-all-engines --enable-engine=scumm-7-8,myst,riven, \
sword1,sword2,sword25,sci,lure,sky,agi,agos
```
Choose whatever engines you want, but if the ELF's .text section exceeds ~10MB-12MB,
you may experience crashes in memory-intensive games such as COMI, Broken Sword and Discworld 2.
Note: using dynamic plugins as suggested is required when building with most or all of the
game engines enabled in order to keep the memory usage low and avoid stability issues.

View File

@ -21,6 +21,8 @@
*/
#include "osystem.h"
#include "backends/plugins/3ds/3ds-provider.h"
#include <3ds.h>
#include <malloc.h>
@ -41,12 +43,9 @@ int main(int argc, char *argv[]) {
g_system = new _3DS::OSystem_3DS();
assert(g_system);
// Invoke the actual ScummVM main entry point
// if (argc > 2)
// res = scummvm_main(argc-2, &argv[2]);
// else
// res = scummvm_main(argc, argv);
// scummvm_main(0, nullptr);
#ifdef DYNAMIC_MODULES
PluginManager::instance().addPluginProvider(new CTRPluginProvider());
#endif
int res = scummvm_main(argc, argv);

View File

@ -0,0 +1,127 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/scummsys.h"
#if defined(DYNAMIC_MODULES) && defined(__3DS__)
#include "backends/plugins/3ds/3ds-provider.h"
#include "backends/plugins/elf/arm-loader.h"
#include "common/debug.h"
#include <3ds.h>
extern uint32 __end__; // End of the main program in memory. Set by the linker.
static uint32 alignUp(uint32 ptr, uint32 align) {
return (ptr + align - 1) & ~(align - 1);
}
class CTRDLObject : public ARMDLObject {
public:
CTRDLObject():
ARMDLObject(),
_segmentHeapAddress(0) {
}
protected:
static const uint32 kPageSize = 0x1000;
uint32 _segmentHeapAddress;
void flushDataCache(void *ptr, uint32 len) const override {
svcFlushProcessDataCache(CUR_PROCESS_HANDLE, ptr, len);
}
void protectMemory(void *ptr, uint32 len, int prot) const override {
debug(2, "elfloader: Protecting memory at %p, len %d with %d", ptr, len, prot);
uint32 ctrFlags = 0;
if (prot & PF_R) ctrFlags |= MEMPERM_READ;
if (prot & PF_W) ctrFlags |= MEMPERM_WRITE;
if (prot & PF_X) ctrFlags |= MEMPERM_EXECUTE;
// The kernel expects the range to be aligned to page boundaries
len = alignUp(len, kPageSize);
Handle currentHandle;
svcDuplicateHandle(&currentHandle, CUR_PROCESS_HANDLE);
svcControlProcessMemory(currentHandle, (uint32)ptr, 0, len, MEMOP_PROT, ctrFlags);
svcCloseHandle(currentHandle);
}
void *allocateMemory(uint32 align, uint32 size) override {
assert(!_segmentHeapAddress); // At the moment we can only load a single segment
_segmentHeapAddress = (uint32)ARMDLObject::allocateMemory(align, size);
if (!_segmentHeapAddress) {
return nullptr;
}
size = alignUp(size, kPageSize);
// The plugin needs to be loaded near the main executable for PC-relative calls
// to resolve. The segment is allocated on the heap which not in the +/-32 MB
// range of the main executable. So here we map the segment address in the heap
// to a virtual address just after the main executable.
uint32 segmentNearAddress = alignUp((uint32)&__end__, kPageSize) + kPageSize;
Handle currentHandle;
svcDuplicateHandle(&currentHandle, CUR_PROCESS_HANDLE);
Result mapResult = svcControlProcessMemory(currentHandle, segmentNearAddress, _segmentHeapAddress, size, MEMOP_MAP, MemPerm(MEMPERM_READ | MEMPERM_WRITE));
svcCloseHandle(currentHandle);
if (mapResult != 0) {
warning("elfloader: unable to map segment memory (%x) near the excutable (%x)", _segmentHeapAddress, segmentNearAddress);
ARMDLObject::deallocateMemory((void *)_segmentHeapAddress, size);
_segmentHeapAddress = 0;
return nullptr;
}
return (void *)segmentNearAddress;
}
void deallocateMemory(void *ptr, uint32 size) override {
assert(_segmentHeapAddress);
uint32 alignedSize = alignUp(size, kPageSize);
Handle currentHandle;
svcDuplicateHandle(&currentHandle, CUR_PROCESS_HANDLE);
svcControlProcessMemory(currentHandle, (uint32)ptr, _segmentHeapAddress, alignedSize, MEMOP_UNMAP, MemPerm(MEMPERM_READ | MEMPERM_WRITE));
svcCloseHandle(currentHandle);
ARMDLObject::deallocateMemory((void *)_segmentHeapAddress, size);
_segmentHeapAddress = 0;
}
};
Plugin *CTRPluginProvider::createPlugin(const Common::FSNode &node) const {
return new TemplatedELFPlugin<CTRDLObject>(node.getPath());
}
#endif // defined(DYNAMIC_MODULES) && defined(__3DS__)

View File

@ -0,0 +1,37 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#if defined(DYNAMIC_MODULES) && defined(__3DS__)
#ifndef BACKENDS_PLUGINS_3DS_PROVIDER_H
#define BACKENDS_PLUGINS_3DS_PROVIDER_H
#include "backends/plugins/elf/elf-provider.h"
class CTRPluginProvider : public ELFPluginProvider {
public:
Plugin *createPlugin(const Common::FSNode &node) const;
};
#endif // BACKENDS_PLUGINS_3DS_PROVIDER_H
#endif // defined(DYNAMIC_MODULES) && defined(__3DS__)

View File

@ -0,0 +1,179 @@
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
PHDRS
{
/* ScummVM's elf loader only allows a single segment, at the moment. */
plugin PT_LOAD FLAGS(7) /* Read | Write | Execute */;
}
SECTIONS
{
/* =========== CODE section =========== */
. = 0;
.text ALIGN(0x1000) :
{
/* .text */
*(.text)
*(.text.*)
*(.glue_7)
*(.glue_7t)
*(.stub)
*(.gnu.warning)
*(.gnu.linkonce.t*)
. = ALIGN(4);
} : plugin
/* =========== RODATA section =========== */
. = ALIGN(0x1000);
.rodata :
{
*(.rodata)
*(.roda)
*(.rodata.*)
*all.rodata*(*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(4);
} : plugin
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } : plugin
__exidx_start = .;
ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } : plugin
__exidx_end = .;
/* =========== DATA section =========== */
. = ALIGN(0x1000);
.data :
{
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(4);
} : plugin
.tdata ALIGN(4) :
{
__tdata_lma = .;
*(.tdata)
*(.tdata.*)
*(.gnu.linkonce.td.*)
. = ALIGN(4);
__tdata_lma_end = .;
} : plugin
.tbss ALIGN(4) :
{
*(.tbss)
*(.tbss.*)
*(.gnu.linkonce.tb.*)
*(.tcommon)
. = ALIGN(4);
} : plugin
.preinit_array ALIGN(4) :
{
PROVIDE (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
} : plugin
.init_array ALIGN(4) :
{
PROVIDE (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE (__init_array_end = .);
} : plugin
.fini_array ALIGN(4) :
{
PROVIDE (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE (__fini_array_end = .);
} : plugin
.ctors ALIGN(4) :
{
___plugin_ctors = .;
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
___plugin_ctors_end = .;
} : plugin
.dtors ALIGN(4) :
{
___plugin_dtors = .;
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
___plugin_dtors_end = .;
} : plugin
__bss_start__ = .;
.bss ALIGN(4) :
{
*(.dynbss)
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b*)
*(COMMON)
. = ALIGN(4);
/* Reserve space for the TLS segment of the main thread */
__tls_start = .;
. += + SIZEOF(.tdata) + SIZEOF(.tbss);
__tls_end = .;
} : plugin
__bss_end__ = .;
__end__ = ABSOLUTE(.) ;
/* ==================
==== Metadata ====
================== */
/* Discard sections that difficult post-processing */
/DISCARD/ : { *(.group .comment .note) }
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
}

View File

@ -65,6 +65,7 @@ bool ARMDLObject::relocate(Elf32_Off offset, Elf32_Word size, byte *relSegment)
// Act differently based on the type of relocation
switch (REL_TYPE(rel[i].r_info)) {
case R_ARM_ABS32:
case R_ARM_TARGET1:
if (sym->st_shndx < SHN_LOPROC) { // Only shift for plugin section.
a = *target; // Get full 32 bits of addend
relocation = a + Elf32_Addr(_segment); // Shift by main offset
@ -80,13 +81,36 @@ bool ARMDLObject::relocate(Elf32_Off offset, Elf32_Word size, byte *relSegment)
break;
case R_ARM_CALL:
debug(8, "elfloader: R_ARM_CALL: PC-relative jump, ld takes care of necessary relocation work for us.");
break;
case R_ARM_JUMP24:
debug(8, "elfloader: R_ARM_JUMP24: PC-relative jump, ld takes care of all relocation work for us.");
break;
if (sym->st_shndx == SHN_ABS) { // Absolute symbols used in PC-relative instructions need to be relocated
// Extract the PC offset from the instruction
int32 pcOffset = *target;
pcOffset = (pcOffset & 0x00ffffff) << 2;
if (pcOffset & 0x02000000)
pcOffset -= 0x04000000;
// Shift by the segment offset
pcOffset -= Elf32_Addr(_segment);
// Check the relocated offset is valid for the instruction
if (pcOffset <= (int32)0xfe000000 ||
pcOffset >= (int32)0x02000000) {
warning("elfloader: R_ARM_CALL/R_ARM_JUMP24: Out of range relocation i=%d, target=%p, pcOffset=%d", i, target, pcOffset);
free(rel);
return false;
}
// Put the relocated offset back in the instruction
pcOffset >>= 2;
pcOffset &= 0x00ffffff;
*target &= (uint32)0xff000000;
*target |= (uint32)pcOffset;
debug(8, "elfloader: R_ARM_CALL/R_ARM_JUMP24: i=%d, origTarget=%x, target=%x", i, origTarget, *target);
}
break;
case R_ARM_V4BX:
debug(8, "elfloader: R_ARM_V4BX: No relocation calculation necessary.");
break;

View File

@ -50,8 +50,7 @@ DLObject::DLObject() :
DLObject::~DLObject() {
discardSymtab();
ELFMemMan.pluginDeallocate(_segment);
_segment = 0;
discardSegment();
}
// Expel the symbol table from memory
@ -65,11 +64,12 @@ void DLObject::discardSymtab() {
_symbol_cnt = 0;
}
// Unload all objects from memory
void DLObject::unload() {
discardSymtab();
ELFMemMan.pluginDeallocate(_segment);
void DLObject::discardSegment() {
if (_segment) {
// Restore default protection before returning memory
protectMemory(_segment, _segmentSize, PF_R | PF_W);
deallocateMemory(_segment, _segmentSize);
}
_segment = 0;
_segmentSize = 0;
@ -77,6 +77,12 @@ void DLObject::unload() {
_segmentVMA = 0;
}
// Unload all objects from memory
void DLObject::unload() {
discardSymtab();
discardSegment();
}
bool DLObject::readElfHeader(Elf32_Ehdr *ehdr) {
assert(_file);
@ -163,7 +169,7 @@ bool DLObject::readProgramHeaders(Elf32_Ehdr *ehdr, Elf32_Phdr *phdr, Elf32_Half
}
bool DLObject::loadSegment(Elf32_Phdr *phdr) {
_segment = (byte *)ELFMemMan.pluginAllocate(phdr->p_align, phdr->p_memsz);
_segment = (byte *)allocateMemory(phdr->p_align, phdr->p_memsz);
if (!_segment) {
warning("elfloader: Out of memory.");
@ -402,6 +408,8 @@ bool DLObject::load() {
return false;
}
protectMemory(_segment, _segmentSize, phdr.p_flags);
return true;
}
@ -488,4 +496,12 @@ void *DLObject::symbol(const char *name) {
return 0;
}
void *DLObject::allocateMemory(uint32 align, uint32 size) {
return ELFMemMan.pluginAllocate(align, size);
}
void DLObject::deallocateMemory(void *ptr, uint32 size) {
ELFMemMan.pluginDeallocate(ptr);
}
#endif /* defined(DYNAMIC_MODULES) && defined(USE_ELF_LOADER) */

View File

@ -68,6 +68,7 @@ protected:
int loadSymbolTable(Elf32_Ehdr *ehdr, Elf32_Shdr *shdr);
bool loadStringTable(Elf32_Shdr *shdr);
virtual void relocateSymbols(ptrdiff_t offset);
void discardSegment();
// architecture specific
@ -83,6 +84,9 @@ protected:
// platform specific
virtual void flushDataCache(void *ptr, uint32 len) const = 0;
virtual void *allocateMemory(uint32 align, uint32 size);
virtual void deallocateMemory(void *ptr, uint32 size);
virtual void protectMemory(void *ptr, uint32 len, int prot) const {};
public:
DLObject();

View File

@ -279,7 +279,7 @@ bool MIPSDLObject::loadSegment(Elf32_Phdr *phdr) {
// We need to take account of non-allocated segment for shorts
if (phdr->p_flags & PF_X) { // This is a relocated segment
// Attempt to allocate memory for segment
_segment = (byte *)ELFMemMan.pluginAllocate(phdr->p_align, phdr->p_memsz);
_segment = (byte *)allocateMemory(phdr->p_align, phdr->p_memsz);
if (!_segment) {
warning("elfloader: Out of memory.");

View File

@ -22,7 +22,7 @@
#include "common/scummsys.h"
#if defined(DYNAMIC_MODULES) && defined(POSIX)
#if defined(DYNAMIC_MODULES) && defined(POSIX) && !defined(__3DS__)
#include "backends/plugins/posix/posix-provider.h"
#include "backends/plugins/dynamic-plugin.h"

10
configure vendored
View File

@ -3986,6 +3986,13 @@ _mak_plugins=
if test "$_dynamic_modules" = yes ; then
echo_n "Checking whether building plugins is supported... "
case $_host_os in
3ds)
_elf_loader=yes
append_var DEFINES "-DUNCACHED_PLUGINS"
_mak_plugins='
PLUGIN_LDFLAGS += -Wl,-T$(srcdir)/backends/plugins/3ds/plugin.ld -march=armv6k -mfloat-abi=hard
'
;;
amigaos)
_plugin_prefix="lib"
_plugin_suffix=".so"
@ -5576,6 +5583,9 @@ case $_host_os in
esac
case $_backend in
3ds)
append_var DEFINES "-DPLUGIN_DIRECTORY=\\\"$datadir/plugins\\\""
;;
openpandora)
# Add ../plugins as a path so plugins can be found when running from a .PND.
append_var DEFINES "-DPLUGIN_DIRECTORY=\\\"../plugins\\\""