This repository has been archived on 2022-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Luma3DS-3GX/sysmodules/pm/source/launch.c

556 lines
20 KiB
C

#include <3ds.h>
#include <string.h>
#include "launch.h"
#include "info.h"
#include "manager.h"
#include "reslimit.h"
#include "exheader_info_heap.h"
#include "task_runner.h"
#include "util.h"
#include "luma.h"
static bool g_debugNextApplication = false;
// Note: official PM has two distinct functions for sysmodule vs. regular app. We refactor that into a single function.
static Result launchTitleImpl(Handle *outDebug, ProcessData **outProcessData, const FS_ProgramInfo *programInfo,
const FS_ProgramInfo *programInfoUpdate, u32 launchFlags, ExHeader_Info *exheaderInfo);
// Note: official PM doesn't include svcDebugActiveProcess in this function, but rather in the caller handling dependencies
static Result loadWithoutDependencies(Handle *outDebug, ProcessData **outProcessData, u64 programHandle, const FS_ProgramInfo *programInfo,
u32 launchFlags, const ExHeader_Info *exheaderInfo)
{
Result res = 0;
Handle processHandle = 0;
u32 pid;
ProcessData *process;
const ExHeader_Arm11SystemLocalCapabilities *localcaps = &exheaderInfo->aci.local_caps;
if (outDebug != NULL) {
*outDebug = 0;
}
if (outProcessData != NULL) {
*outProcessData = NULL;
}
if (programInfo->programId & (1ULL << 35)) {
// RequireBatchUpdate?
return 0xD8E05803;
}
TRY(LOADER_LoadProcess(&processHandle, programHandle));
TRY(svcGetProcessId(&pid, processHandle));
// Note: bug in official PM: it seems not to panic/cleanup properly if the function calls below fail,
// svcTerminateProcess won't be called, it's possible to trigger NULL derefs if you crash fs/sm/whatever,
// leaks dependencies, and so on...
// This can be solved by interesting the new process in the list earlier, etc. etc., allowing us to simplify the logic greatly.
ProcessList_Lock(&g_manager.processList);
process = ProcessList_New(&g_manager.processList);
if (process == NULL) {
panic(1);
}
process->handle = processHandle;
process->pid = pid;
process->titleId = exheaderInfo->aci.local_caps.title_id;;
process->programHandle = programHandle;
process->launchFlags = launchFlags; // not in official PM
process->flags = 0; // will be filled later
process->terminatedNotificationVariation = (launchFlags & 0xF0) >> 4;
process->terminationStatus = TERMSTATUS_RUNNING;
process->refcount = 1;
process->mediaType = programInfo->mediaType; // not in official PM
ProcessList_Unlock(&g_manager.processList);
svcSignalEvent(g_manager.newProcessEvent);
if (outProcessData != NULL) {
*outProcessData = process;
}
u32 serviceCount;
for(serviceCount = 0; serviceCount < 34 && *(u64 *)localcaps->service_access[serviceCount] != 0; serviceCount++);
// Not in official PM: patch local caps to give access to everything
ExHeader_Arm11StorageInfo storageInfo = localcaps->storage_info;
storageInfo.fs_access_info = 0xFFFFFFFF;
TRY(FSREG_Register(pid, programHandle, programInfo, &storageInfo));
TRY(SRVPM_RegisterProcess(pid, serviceCount, localcaps->service_access));
if (localcaps->reslimit_category <= RESLIMIT_CATEGORY_OTHER) {
TRY(svcSetProcessResourceLimits(processHandle, g_manager.reslimits[localcaps->reslimit_category]));
}
// Yes, even numberOfCores=2 on N3DS. On the 3DS, the affinity mask doesn't play the role of an access limiter,
// it's only useful for cpuId < 0. thread->affinityMask = process->affinityMask | (cpuId >= 0 ? 1 << cpuId : 0)
u8 affinityMask = localcaps->core_info.affinity_mask;
TRY(svcSetProcessAffinityMask(processHandle, &affinityMask, 2));
TRY(svcSetProcessIdealProcessor(processHandle, localcaps->core_info.ideal_processor));
if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) {
setAppCpuTimeLimitAndSchedModeFromDescriptor(localcaps->title_id, localcaps->reslimits[0]);
(*outProcessData)->flags |= PROCESSFLAG_NORMAL_APPLICATION; // not in official PM
}
if (outDebug != NULL) {
TRY(svcDebugActiveProcess(outDebug, pid));
}
return res;
}
static Result loadWithDependencies(Handle *outDebug, ProcessData **outProcessData, u64 programHandle, const FS_ProgramInfo *programInfo,
u32 launchFlags, const ExHeader_Info *exheaderInfo)
{
Result res = 0;
u64 dependencies[48];
u32 remrefcounts[48] = {0};
ProcessData *depProcs[48] = {NULL};
u32 numUnique = 0;
FS_ProgramInfo depProgramInfo;
res = loadWithoutDependencies(outDebug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo);
ProcessData *process = *outProcessData;
if (R_FAILED(res)) {
if (outDebug != NULL) {
svcCloseHandle(*outDebug);
*outDebug = 0;
}
if (process != NULL) {
svcTerminateProcess(process->handle);
}
return res;
}
ExHeader_Info *depExheaderInfo = ExHeaderInfoHeap_New();
if (depExheaderInfo == NULL) {
panic(0);
}
listMergeUniqueDependencies(depProcs, dependencies, remrefcounts, &numUnique, exheaderInfo);
if (numUnique > 0) {
process->flags |= PROCESSFLAG_DEPENDENCIES_LOADED;
}
if (launchFlags & PMLAUNCHFLAGEXT_FAKE_DEPENDENCY_LOADING) {
// See no evil
numUnique = 0;
}
/*
Official pm does this:
for each dependency:
if dep already loaded: if autoloaded increase refcount // note: not autoloaded = not autoterminated
else: load new sysmodule w/o its deps (then process its deps), set flag "autoloaded" return early from entire function if it fails
Naturally, it forgets to incref all subsequent dependencies here & also when it factors the duplicate entries in,
and has a few other bugs (actually I'm not entirely sure... I think it doesn't clear dependencies on termination if it fails)
It also has a buffer overflow bug if the flattened dep tree has more than 48 elements (but this can never happen in practice)
*/
for (u32 i = 0; i < numUnique; i++) {
if (depProcs[i] != NULL) {
continue;
}
// Note: numUnique is changed within the loop
depProgramInfo.programId = dependencies[i];
depProgramInfo.mediaType = MEDIATYPE_NAND;
res = launchTitleImpl(NULL, &process, &depProgramInfo, NULL, 0, depExheaderInfo);
depProcs[i] = process;
if (R_SUCCEEDED(res)) {
process->flags |= PROCESSFLAG_AUTOLOADED | PROCESSFLAG_DEPENDENCIES_LOADED;
ProcessData_Incref(process, remrefcounts[i] - 1);
remrefcounts[i] = 0;
listMergeUniqueDependencies(depProcs, dependencies, remrefcounts, &numUnique, depExheaderInfo); // does some incref too
} else if (process != NULL) {
if (outDebug != NULL) {
svcCloseHandle(*outDebug);
*outDebug = 0;
}
svcTerminateProcess(process->handle);
ExHeaderInfoHeap_Delete(depExheaderInfo);
return res;
}
}
ExHeaderInfoHeap_Delete(depExheaderInfo);
return res;
}
// Note: official PM has two distinct functions for sysmodule vs. regular app. We refactor that into a single function.
static Result launchTitleImpl(Handle *debug, ProcessData **outProcessData, const FS_ProgramInfo *programInfo,
const FS_ProgramInfo *programInfoUpdate, u32 launchFlags, ExHeader_Info *exheaderInfo)
{
*outProcessData = NULL;
if (debug != NULL) {
*debug = 0;
}
if (isTitleLaunchPrevented(programInfo->programId)) {
return 0;
}
if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) {
launchFlags |= PMLAUNCHFLAG_LOAD_DEPENDENCIES;
} else {
// NEW: allow non-apps to be debugged with the help of pm
//launchFlags &= ~(PMLAUNCHFLAG_USE_UPDATE_TITLE | PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION);
launchFlags &= ~PMLAUNCHFLAG_USE_UPDATE_TITLE;
launchFlags &= ~(PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM | PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM);
}
Result res = 0;
u64 programHandle;
StartupInfo si = {0};
programInfoUpdate = (launchFlags & PMLAUNCHFLAG_USE_UPDATE_TITLE) ? programInfoUpdate : programInfo;
TRY(registerProgram(&programHandle, programInfo, programInfoUpdate));
u32 coreVer = OS_KernelConfig->kernel_syscore_ver;
res = LOADER_GetProgramInfo(exheaderInfo, programHandle);
res = R_SUCCEEDED(res) && coreVer == 2 && exheaderInfo->aci.local_caps.core_info.core_version != coreVer ? (Result)0xC8A05800 : res;
if (R_FAILED(res)) {
LOADER_UnregisterProgram(programHandle);
return res;
}
// Change APPMEMALLOC if needed
if (IS_N3DS && OS_KernelConfig->app_memtype == 6 && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) {
u32 limitMb;
SystemMode n3dsSystemMode = exheaderInfo->aci.local_caps.core_info.n3ds_system_mode;
bool forceO3dsAppMem = (launchFlags & PMLAUNCHFLAG_FORCE_USE_O3DS_APP_MEM) != 0;
if (forceO3dsAppMem && (launchFlags & PMLAUNCHFLAG_FORCE_USE_O3DS_MAX_APP_MEM) != 0) {
setAppMemLimit(96 << 20);
} else if (forceO3dsAppMem || n3dsSystemMode == SYSMODE_O3DS_PROD) {
switch (exheaderInfo->aci.local_caps.core_info.o3ds_system_mode) {
case SYSMODE_O3DS_PROD: limitMb = 64; break;
case SYSMODE_DEV1: limitMb = 96; break;
case SYSMODE_DEV2: limitMb = 80; break;
default: limitMb = 0; break;
}
// Can be 0:
setAppMemLimit(limitMb << 20);
}
}
if (launchFlags & PMLAUNCHFLAG_LOAD_DEPENDENCIES) {
TRYG(loadWithDependencies(debug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo), cleanup);
// note: official pm doesn't terminate the process if this fails (dependency loading)...
// This may be intentional, but I believe this is a bug since the 0xD8A05805 and svcRun failure codepaths terminate the process...
// It also forgets to clear PROCESSFLAG_NOTIFY_TERMINATION in the process...
} else {
TRYG(loadWithoutDependencies(debug, outProcessData, programHandle, programInfo, launchFlags, exheaderInfo), cleanup);
// note: official pm doesn't terminate the proc. if it fails here either, but will because of the svcCloseHandle and the svcRun codepath
}
ProcessList_Lock(&g_manager.processList);
ProcessData *process = *outProcessData;
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) {
// saved field is different in official pm
// this also means official pm can't launch a title with a debug flag and an application
if (g_manager.debugData == NULL) {
g_manager.debugData = process;
} else {
res = 0xD8A05805;
}
} else {
si.priority = exheaderInfo->aci.local_caps.core_info.priority;
si.stack_size = exheaderInfo->sci.codeset_info.stack_size;
res = svcRun(process->handle, &si);
if (R_SUCCEEDED(res) && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) {
g_manager.runningApplicationData = process;
notifySubscribers(0x10C);
}
}
cleanup:
process = *outProcessData;
if (process != NULL && R_FAILED(res)) {
svcTerminateProcess(process->handle);
} else if (process != NULL) {
// official PM sets it but forgets to clear it on failure...
process->flags |= (launchFlags & PMLAUNCHFLAG_NOTIFY_TERMINATION) ? PROCESSFLAG_NOTIFY_TERMINATION : 0;
}
ProcessList_Unlock(&g_manager.processList);
return res;
}
static Result launchTitleImplWrapper(Handle *outDebug, u32 *outPid, const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags)
{
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
ProcessData *process = NULL;
Result res = launchTitleImpl(outDebug, &process, programInfo, programInfoUpdate, launchFlags, exheaderInfo);
if (outPid != NULL && process != NULL) {
*outPid = process->pid;
}
ExHeaderInfoHeap_Delete(exheaderInfo);
return res;
}
static void LaunchTitleAsync(void *argdata)
{
struct {
FS_ProgramInfo programInfo, programInfoUpdate;
u32 launchFlags;
} *args = argdata;
launchTitleImplWrapper(NULL, NULL, &args->programInfo, &args->programInfoUpdate, args->launchFlags);
}
Result LaunchTitle(u32 *outPid, const FS_ProgramInfo *programInfo, u32 launchFlags)
{
ProcessData *process, *foundProcess = NULL;
bool originallyDebugged = launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION;
launchFlags &= ~PMLAUNCHFLAG_USE_UPDATE_TITLE;
launchFlags |= g_debugNextApplication && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) ? PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION : 0;
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
u32 tidh = (u32)(programInfo->programId >> 32);
u32 tidl = (u32)programInfo->programId;
u32 coreVer = OS_KernelConfig->kernel_syscore_ver;
if (coreVer == 2 && (tidh == 0x00040030 || tidh == 0x00040130) && (tidl & 0xFF) != coreVer) {
// Panic if launching SAFE_MODE sysmodules or applets (note: exheader syscorever check above only done for applications in official PM)
// Official PM also hardcodes SYSCOREVER = 2 here.
// NATIVE_FIRM-only.
panic(4);
}
ProcessList_Lock(&g_manager.processList);
if (g_manager.runningApplicationData != NULL && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) != 0) {
ProcessList_Unlock(&g_manager.processList);
return 0xC8A05BF0;
}
FOREACH_PROCESS(&g_manager.processList, process) {
if ((process->titleId & ~0xFFULL) == (programInfo->programId & ~0xFFULL)) {
foundProcess = process;
break;
}
}
ProcessList_Unlock(&g_manager.processList);
if (foundProcess != NULL) {
foundProcess->flags &= ~PROCESSFLAG_AUTOLOADED;
if (outPid != NULL) {
*outPid = foundProcess->pid;
}
return 0;
} else {
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION || !(launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION)) {
Result res = launchTitleImplWrapper(NULL, outPid, programInfo, programInfo, launchFlags);
if (R_SUCCEEDED(res) && (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION)) {
g_debugNextApplication = false;
if (!originallyDebugged) {
// Custom notification
notifySubscribers(0x1000);
}
}
return res;
} else {
struct {
FS_ProgramInfo programInfo, programInfoUpdate;
u32 launchFlags;
} args = { *programInfo, *programInfo, launchFlags };
if (outPid != NULL) {
*outPid = (u32)-1; // PM doesn't do that lol
}
TaskRunner_RunTask(LaunchTitleAsync, &args, sizeof(args));
return 0;
}
}
}
Result LaunchTitleUpdate(const FS_ProgramInfo *programInfo, const FS_ProgramInfo *programInfoUpdate, u32 launchFlags)
{
ProcessList_Lock(&g_manager.processList);
if (g_manager.preparingForReboot) {
return 0xC8A05801;
}
if (g_manager.runningApplicationData != NULL) {
ProcessList_Unlock(&g_manager.processList);
return 0xC8A05BF0;
}
if (!(launchFlags & ~PMLAUNCHFLAG_NORMAL_APPLICATION)) {
return 0xD8E05802;
}
ProcessList_Unlock(&g_manager.processList);
bool originallyDebugged = launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION;
launchFlags |= PMLAUNCHFLAG_USE_UPDATE_TITLE;
launchFlags |= g_debugNextApplication ? PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION : 0;
if (launchFlags & PMLAUNCHFLAG_QUEUE_DEBUG_APPLICATION) {
Result res = launchTitleImplWrapper(NULL, NULL, programInfo, programInfoUpdate, launchFlags);
if (R_SUCCEEDED(res)) {
g_debugNextApplication = false;
if (!originallyDebugged) {
// Custom notification
notifySubscribers(0x1000);
}
}
return res;
} else {
struct {
FS_ProgramInfo programInfo, programInfoUpdate;
u32 launchFlags;
} args = { *programInfo, *programInfoUpdate, launchFlags };
TaskRunner_RunTask(LaunchTitleAsync, &args, sizeof(args));
return 0;
}
}
Result LaunchApp(const FS_ProgramInfo *programInfo, u32 launchFlags)
{
ProcessList_Lock(&g_manager.processList);
if (g_manager.runningApplicationData != NULL) {
ProcessList_Unlock(&g_manager.processList);
return 0xC8A05BF0;
}
assertSuccess(setAppCpuTimeLimit(0));
ProcessList_Unlock(&g_manager.processList);
return LaunchTitle(NULL, programInfo, launchFlags | PMLAUNCHFLAG_LOAD_DEPENDENCIES | PMLAUNCHFLAG_NORMAL_APPLICATION);
}
Result RunQueuedProcess(Handle *outDebug)
{
Result res = 0;
StartupInfo si = {0};
ProcessList_Lock(&g_manager.processList);
if (g_manager.debugData == NULL) {
ProcessList_Unlock(&g_manager.processList);
return 0xD8A05804;
} else if ((g_manager.debugData->flags & PROCESSFLAG_NORMAL_APPLICATION) && g_manager.runningApplicationData != NULL) {
// Not in official PM
ProcessList_Unlock(&g_manager.processList);
return 0xC8A05BF0;
}
ProcessData *process = g_manager.debugData;
g_manager.debugData = NULL;
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
TRYG(LOADER_GetProgramInfo(exheaderInfo, process->programHandle), cleanup);
TRYG(svcDebugActiveProcess(outDebug, process->pid), cleanup);
si.priority = exheaderInfo->aci.local_caps.core_info.priority;
si.stack_size = exheaderInfo->sci.codeset_info.stack_size;
res = svcRun(process->handle, &si);
if (R_SUCCEEDED(res) && process->flags & PROCESSFLAG_NORMAL_APPLICATION) {
// Second operand not in official PM
g_manager.runningApplicationData = process;
notifySubscribers(0x10C);
}
cleanup:
if (R_FAILED(res)) {
process->flags &= ~PROCESSFLAG_NOTIFY_TERMINATION;
svcTerminateProcess(process->handle);
}
ExHeaderInfoHeap_Delete(exheaderInfo);
ProcessList_Unlock(&g_manager.processList);
return res;
}
Result LaunchAppDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags)
{
ProcessList_Lock(&g_manager.processList);
if (g_manager.debugData != NULL) {
ProcessList_Unlock(&g_manager.processList);
return RunQueuedProcess(outDebug);
}
if (g_manager.runningApplicationData != NULL) {
ProcessList_Unlock(&g_manager.processList);
return 0xC8A05BF0;
}
bool prevdbg = g_debugNextApplication;
g_debugNextApplication = false;
assertSuccess(setAppCpuTimeLimit(0));
ProcessList_Unlock(&g_manager.processList);
Result res = launchTitleImplWrapper(outDebug, NULL, programInfo, programInfo,
(launchFlags & ~PMLAUNCHFLAG_USE_UPDATE_TITLE) | PMLAUNCHFLAG_NORMAL_APPLICATION);
if (R_FAILED(res)) {
g_debugNextApplication = prevdbg;
}
return res;
}
Result autolaunchSysmodules(void)
{
Result res = 0;
FS_ProgramInfo programInfo = { .mediaType = MEDIATYPE_NAND };
// Launch NS
if (OS_KernelConfig->ns_tid != 0) {
programInfo.programId = OS_KernelConfig->ns_tid;
TRY(launchTitleImplWrapper(NULL, NULL, &programInfo, &programInfo, PMLAUNCHFLAG_LOAD_DEPENDENCIES));
}
return res;
}
// Custom
Result DebugNextApplicationByForce(bool debug)
{
g_debugNextApplication = debug;
return 0;
}
Result LaunchTitleDebug(Handle *outDebug, const FS_ProgramInfo *programInfo, u32 launchFlags)
{
if (launchFlags & PMLAUNCHFLAG_NORMAL_APPLICATION) {
return LaunchAppDebug(outDebug, programInfo, launchFlags);
}
ProcessList_Lock(&g_manager.processList);
if (g_manager.debugData != NULL) {
ProcessList_Unlock(&g_manager.processList);
return RunQueuedProcess(outDebug);
}
ProcessList_Unlock(&g_manager.processList);
return launchTitleImplWrapper(outDebug, NULL, programInfo, programInfo, launchFlags & ~PMLAUNCHFLAG_USE_UPDATE_TITLE);
}