Luma3DS-autorun/sysmodules/pm/source/process_monitor.c
TuxSH 1d7cca25d1 Fix long-standing bug affecting hbldr TID change option
This was a long-standing bug since Luma3DS v8.0.

If you changed the homebrew title and didn't immediately reboot, then
the ExHeader during termination would not match the ExHeader that was
was used for loading the process, and thus sysmodule process refcounts
would get all messed up.

The obvious solution to this is to ensure no application is running
while changing the hbldr titleID (hence the changes in custom PM).

This was quite possibly one of the cause of homebrew failing to load
when using N3DS H&S.
2023-02-08 16:14:59 +00:00

118 lines
4.3 KiB
C

#include <3ds.h>
#include <string.h>
#include "process_monitor.h"
#include "exheader_info_heap.h"
#include "termination.h"
#include "reslimit.h"
#include "manager.h"
#include "util.h"
#include "luma_shared_config.h"
static void cleanupProcess(ProcessData *process)
{
if (process->flags & PROCESSFLAG_DEPENDENCIES_LOADED) {
ExHeader_Info *exheaderInfo = ExHeaderInfoHeap_New();
if (exheaderInfo == NULL) {
panic(0);
}
listAndTerminateDependencies(process, exheaderInfo);
ExHeaderInfoHeap_Delete(exheaderInfo);
}
if (!(process->flags & PROCESSFLAG_KIP)) {
SRVPM_UnregisterProcess(process->pid);
FSREG_Unregister(process->pid);
LOADER_UnregisterProgram(process->programHandle);
}
ProcessList_Lock(&g_manager.processList);
if (g_manager.runningApplicationData != NULL && process->handle == g_manager.runningApplicationData->handle) {
if (IS_N3DS && OS_KernelConfig->app_memtype == 6) {
assertSuccess(resetAppMemLimit());
}
g_manager.runningApplicationData = NULL;
// We need to do this here to ensure that the ExHeader at init matches the ExHeader
// at termination at all times, otherwise the process refcounts of sysmodules
// get all messed up.
Luma_SharedConfig->hbldr_3dsx_tid = Luma_SharedConfig->selected_hbldr_3dsx_tid;
}
if (g_manager.debugData != NULL && process->handle == g_manager.debugData->handle) {
g_manager.debugData = NULL;
}
ProcessList_Unlock(&g_manager.processList);
if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) {
notifySubscribers(0x110 + process->terminatedNotificationVariation);
}
}
void processMonitor(void *p)
{
(void)p;
Handle handles[0x41] = { g_manager.newProcessEvent };
for (;;) {
u32 numProcesses = 0;
bool atLeastOneTerminating = false;
ProcessData *process;
ProcessData processBackup;
s32 id = -1;
ProcessList_Lock(&g_manager.processList);
FOREACH_PROCESS(&g_manager.processList, process) {
// Rebuild the handle array
if (process->terminationStatus != TERMSTATUS_TERMINATED) {
handles[1 + numProcesses++] = process->handle;
if (process->terminationStatus == TERMSTATUS_NOTIFICATION_SENT) {
atLeastOneTerminating = true;
}
}
}
ProcessList_Unlock(&g_manager.processList);
// If no more processes are terminating, signal the event
if (g_manager.waitingForTermination && !atLeastOneTerminating) {
assertSuccess(svcSignalEvent(g_manager.allNotifiedTerminationEvent));
}
// Note: lack of assertSuccess is intentional.
svcWaitSynchronizationN(&id, handles, 1 + numProcesses, false, -1LL);
if (id > 0) {
// Note: official PM conditionally erases the process from the list, cleans up, then conditionally frees the process data
// Bug in official PM (?): it unlocks the list before setting termstatus = TERMSTATUS_TERMINATED
ProcessList_Lock(&g_manager.processList);
process = ProcessList_FindProcessByHandle(&g_manager.processList, handles[id]);
if (process != NULL) {
process->terminationStatus = TERMSTATUS_TERMINATED;
if (process->flags & PROCESSFLAG_NOTIFY_TERMINATION) {
process->flags |= PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED;
}
processBackup = *process; // <-- make sure no list access is done through this node
// Note: PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED can be set by terminateProcessImpl
// APT is shit, why must an app call APT to ask to terminate itself?
if (!(process->flags & PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED)) {
ProcessList_Delete(&g_manager.processList, process);
}
}
ProcessList_Unlock(&g_manager.processList);
if (process != NULL) {
cleanupProcess(&processBackup);
if (!(processBackup.flags & PROCESSFLAG_NOTIFY_TERMINATION_TERMINATED)) {
svcCloseHandle(processBackup.handle);
}
}
}
}
}