Complete making the kernel thread-safe #2 + Fix Shared Memory Implementation

This commit makes the kernel completely thread-safe and fixes an issue that caused libNX games to not work due to an error with KSharedMemory. In addition, implement GroupMutex to allow the kernel threads to run in parallel but still allow them to not overlap with the JNI thread.
This commit is contained in:
◱ PixelyIon 2020-01-11 10:22:25 +05:30 committed by ◱ PixelyIon
parent de6d8d8f48
commit 65018aedbc
39 changed files with 547 additions and 932 deletions

View File

@ -38,7 +38,6 @@ add_library(skyline SHARED
${source_DIR}/skyline/loader/nro.cpp
${source_DIR}/skyline/kernel/ipc.cpp
${source_DIR}/skyline/kernel/svc.cpp
${source_DIR}/skyline/kernel/types/KSyncObject.cpp
${source_DIR}/skyline/kernel/types/KProcess.cpp
${source_DIR}/skyline/kernel/types/KThread.cpp
${source_DIR}/skyline/kernel/types/KSharedMemory.cpp
@ -59,5 +58,5 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/vi/vi_m.cpp
)
target_link_libraries(skyline vulkan GLESv3 EGL android fmt tinyxml2)
target_link_libraries(skyline vulkan android fmt tinyxml2)
target_compile_options(skyline PRIVATE -Wno-c++17-extensions)

View File

@ -0,0 +1,155 @@
---
Checks: 'clang-diagnostic-*,clang-analyzer-*,*, android-*, -bugprone-bool-pointer-implicit-conversion, -cert-env33-c, -cert-dcl50-cpp, -cert-dcl59-cpp, -cppcoreguidelines-no-malloc, -cppcoreguidelines-owning-memory, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-cstyle-cast, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, -fuchsia-*, -google-*, google-default-arguments, google-explicit-constructor, google-runtime-member-string-references, google-runtime-operator, -hicpp-braces-around-statements,
-hicpp-braces-around-statements, -hicpp-named-parameter, -hicpp-no-array-decay, -hicpp-no-assembler, -hicpp-no-malloc, -hicpp-function-size, -hicpp-special-member-functions, -hicpp-vararg, -llvm-*, -objc-*, -readability-else-after-return, -readability-implicit-bool-conversion, -readability-named-parameter, -readability-simplify-boolean-expr, -readability-braces-around-statements,
-readability-identifier-naming, -readability-function-size, -readability-redundant-member-init, -misc-bool-pointer-implicit-conversion, -misc-definitions-in-headers, -misc-unused-alias-decls, -misc-unused-parameters, -misc-unused-using-decls, -modernize-use-using, -modernize-use-default-member-init, -clang-diagnostic-*, -clang-analyzer-*, -hicpp-signed-bitwise'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: none
CheckOptions:
- key: bugprone-argument-comment.StrictMode
value: '0'
- key: bugprone-assert-side-effect.AssertMacros
value: assert
- key: bugprone-assert-side-effect.CheckFunctionCalls
value: '0'
- key: bugprone-dangling-handle.HandleClasses
value: 'std::basic_string_view;std::experimental::basic_string_view'
- key: bugprone-string-constructor.LargeLengthThreshold
value: '8388608'
- key: bugprone-string-constructor.WarnOnLargeLength
value: '1'
- key: cert-err09-cpp.CheckThrowTemporaries
value: '1'
- key: cert-err61-cpp.CheckThrowTemporaries
value: '1'
- key: cert-oop11-cpp.IncludeStyle
value: llvm
- key: cppcoreguidelines-pro-type-member-init.IgnoreArrays
value: '0'
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: hicpp-member-init.IgnoreArrays
value: '0'
- key: hicpp-move-const-arg.CheckTriviallyCopyableMove
value: '1'
- key: hicpp-use-auto.RemoveStars
value: '0'
- key: hicpp-use-emplace.ContainersWithPushBack
value: '::std::vector;::std::list;::std::deque'
- key: hicpp-use-emplace.SmartPointers
value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr'
- key: hicpp-use-emplace.TupleMakeFunctions
value: '::std::make_pair;::std::make_tuple'
- key: hicpp-use-emplace.TupleTypes
value: '::std::pair;::std::tuple'
- key: hicpp-use-equals-default.IgnoreMacros
value: '1'
- key: hicpp-use-noexcept.ReplacementString
value: ''
- key: hicpp-use-noexcept.UseNoexceptFalse
value: '1'
- key: hicpp-use-nullptr.NullMacros
value: ''
- key: misc-misplaced-widening-cast.CheckImplicitCasts
value: '0'
- key: misc-sizeof-expression.WarnOnSizeOfCompareToConstant
value: '1'
- key: misc-sizeof-expression.WarnOnSizeOfConstant
value: '1'
- key: misc-sizeof-expression.WarnOnSizeOfThis
value: '1'
- key: misc-suspicious-enum-usage.StrictMode
value: '0'
- key: misc-suspicious-missing-comma.MaxConcatenatedTokens
value: '5'
- key: misc-suspicious-missing-comma.RatioThreshold
value: '0.200000'
- key: misc-suspicious-missing-comma.SizeThreshold
value: '5'
- key: misc-suspicious-string-compare.StringCompareLikeFunctions
value: ''
- key: misc-suspicious-string-compare.WarnOnImplicitComparison
value: '1'
- key: misc-suspicious-string-compare.WarnOnLogicalNotComparison
value: '0'
- key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries
value: '1'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-make-shared.IgnoreMacros
value: '1'
- key: modernize-make-shared.IncludeStyle
value: '0'
- key: modernize-make-shared.MakeSmartPtrFunction
value: 'std::make_shared'
- key: modernize-make-shared.MakeSmartPtrFunctionHeader
value: memory
- key: modernize-make-unique.IgnoreMacros
value: '1'
- key: modernize-make-unique.IncludeStyle
value: '0'
- key: modernize-make-unique.MakeSmartPtrFunction
value: 'std::make_unique'
- key: modernize-make-unique.MakeSmartPtrFunctionHeader
value: memory
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-pass-by-value.ValuesOnly
value: '0'
- key: modernize-raw-string-literal.ReplaceShorterLiterals
value: '0'
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-replace-random-shuffle.IncludeStyle
value: llvm
- key: modernize-use-auto.RemoveStars
value: '0'
- key: modernize-use-emplace.ContainersWithPushBack
value: '::std::vector;::std::list;::std::deque'
- key: modernize-use-emplace.SmartPointers
value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr'
- key: modernize-use-emplace.TupleMakeFunctions
value: '::std::make_pair;::std::make_tuple'
- key: modernize-use-emplace.TupleTypes
value: '::std::pair;::std::tuple'
- key: modernize-use-equals-default.IgnoreMacros
value: '1'
- key: modernize-use-noexcept.ReplacementString
value: ''
- key: modernize-use-noexcept.UseNoexceptFalse
value: '1'
- key: modernize-use-nullptr.NullMacros
value: 'NULL'
- key: modernize-use-transparent-functors.SafeMode
value: '0'
- key: performance-faster-string-find.StringLikeClasses
value: 'std::basic_string'
- key: performance-for-range-copy.WarnOnAllAutoCopies
value: '0'
- key: performance-inefficient-string-concatenation.StrictMode
value: '0'
- key: performance-inefficient-vector-operation.VectorLikeClasses
value: '::std::vector'
- key: performance-move-const-arg.CheckTriviallyCopyableMove
value: '1'
- key: performance-move-constructor-init.IncludeStyle
value: llvm
- key: performance-type-promotion-in-math-fn.IncludeStyle
value: llvm
- key: performance-unnecessary-value-param.IncludeStyle
value: llvm
- key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold
value: '3'
...

View File

@ -7,7 +7,7 @@
bool Halt;
jobject Surface;
uint FaultCount;
skyline::Mutex jniMtx;
skyline::GroupMutex jniMtx;
void signalHandler(int signal) {
syslog(LOG_ERR, "Halting program due to signal: %s", strsignal(signal));
@ -56,13 +56,13 @@ extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_executeRom(JNIEnv *env,
}
extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_setHalt(JNIEnv *env, jobject instance, jboolean halt) {
jniMtx.lock();
jniMtx.lock(skyline::GroupMutex::Group::Group2);
Halt = halt;
jniMtx.unlock();
}
extern "C" JNIEXPORT void Java_emu_skyline_GameActivity_setSurface(JNIEnv *env, jobject instance, jobject surface) {
jniMtx.lock();
jniMtx.lock(skyline::GroupMutex::Group::Group2);
if (!env->IsSameObject(Surface, nullptr))
env->DeleteGlobalRef(Surface);
if (!env->IsSameObject(surface, nullptr))

View File

@ -16,6 +16,17 @@ namespace skyline {
return !flag.test_and_set(std::memory_order_acquire);
}
void GroupMutex::lock(Group group) {
auto none = Group::None;
while (!flag.compare_exchange_weak(none, group) && flag != group);
num++;
}
void GroupMutex::unlock() {
if (!--num)
flag.exchange(Group::None);
}
Settings::Settings(const int preferenceFd) {
tinyxml2::XMLDocument pref;
if (pref.LoadFile(fdopen(preferenceFd, "r")))

View File

@ -42,7 +42,7 @@ namespace skyline {
constexpr u16 BrkRdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready
constexpr u32 TpidrroEl0 = 0x5E83; //!< ID of TPIDRRO_EL0 in MRS
constexpr u32 CntfrqEl0 = 0x5F00; //!< ID of CNTFRQ_EL0 in MRS
constexpr u32 TegraX1Freq = 0x124F800; //!< The clock frequency of the Tegra X1
constexpr u32 TegraX1Freq = 0x124F800; //!< The clock frequency of the Tegra X1 (19.2 MHz)
constexpr u32 CntpctEl0 = 0x5F01; //!< ID of CNTPCT_EL0 in MRS
constexpr u32 CntvctEl0 = 0x5F02; //!< ID of CNTVCT_EL0 in MRS
// Kernel
@ -111,7 +111,7 @@ namespace skyline {
void lock();
/**
* @brief Lock the mutex if it is unlocked else return
* @brief Try to lock the mutex if it is unlocked else return
* @return If the mutex was successfully locked or not
*/
bool try_lock();
@ -122,6 +122,36 @@ namespace skyline {
void unlock();
};
/**
* @brief The GroupMutex class is a special type of mutex that allows two groups of users and only allows one group to run in parallel
*/
class GroupMutex {
public:
/**
* @brief This enumeration holds all the possible owners of the mutex
*/
enum class Group : u8 {
None = 0, //!< No group owns this mutex
Group1 = 1, //!< Group 1 owns this mutex
Group2 = 2 //!< Group 2 owns this mutex
};
/**
* @brief Wait on and lock the mutex
*/
void lock(Group group = Group::Group1);
/**
* @brief Unlock the mutex
* @note Undefined behavior in case unlocked by thread in non-owner group
*/
void unlock();
private:
std::atomic<Group> flag = Group::None; //!< An atomic flag to hold which group holds the mutex
std::atomic<u8> num = 0; //!< An atomic u8 keeping track of how many users are holding the mutex
};
/**
* @brief The Logger class is to write log output to file and logcat
*/

View File

@ -62,8 +62,8 @@ namespace skyline::gpu {
u8 *inBlock = inBuffer;
u8 *outBlock = outBuffer + (y * strideBytes) + x;
for (u32 i = 0; i < 32; i++) {
const u32 yT = ((i >> 1) & 0x06) | (i & 0x01); // NOLINT(hicpp-signed-bitwise)
const u32 xT = ((i << 3) & 0x10) | ((i << 1) & 0x20); // NOLINT(hicpp-signed-bitwise)
const u32 yT = ((i >> 1) & 0x06) | (i & 0x01);
const u32 xT = ((i << 3) & 0x10) | ((i << 1) & 0x20);
std::memcpy(outBlock + (yT * strideBytes) + xT, inBlock, sizeof(u128));
inBlock += sizeof(u128);
}

View File

@ -34,10 +34,6 @@ namespace skyline::gpu {
state.process->ReadMemory(dataBuffer.data(), nvBuffer->address + gbpBuffer.offset, gbpBuffer.size);
}
BufferQueue::WaitContext::WaitContext(std::shared_ptr<kernel::type::KThread> thread, DequeueIn input, kernel::ipc::OutputBuffer& buffer) : thread(std::move(thread)), input(input), buffer(buffer) {}
BufferQueue::DequeueOut::DequeueOut(u32 slot) : slot(slot), _unk0_(0x1), _unk1_(0x24) {}
BufferQueue::BufferQueue(const DeviceState &state) : state(state) {}
void BufferQueue::RequestBuffer(Parcel &in, Parcel &out) {
@ -50,7 +46,13 @@ namespace skyline::gpu {
}
void BufferQueue::DequeueBuffer(Parcel &in, Parcel &out) {
auto *data = reinterpret_cast<DequeueIn *>(in.data.data() + constant::TokenLength);
struct Data {
u32 format;
u32 width;
u32 height;
u32 timestamps;
u32 usage;
} *data = reinterpret_cast<Data *>(in.data.data() + constant::TokenLength);
i64 slot{-1};
while (slot == -1) {
for (auto &buffer : queue) {
@ -62,7 +64,12 @@ namespace skyline::gpu {
}
sched_yield();
}
DequeueOut output(static_cast<u32>(slot));
struct {
u32 slot;
u32 _unk_[13];
} output{
.slot = static_cast<u32>(slot)
};
out.WriteData(output);
state.logger->Debug("DequeueBuffer: Width: {}, Height: {}, Format: {}, Usage: {}, Timestamps: {}, Slot: {}", data->width, data->height, data->format, data->usage, data->timestamps, slot);
}
@ -118,28 +125,19 @@ namespace skyline::gpu {
auto gbpBuffer = reinterpret_cast<GbpBuffer *>(pointer);
queue[data->slot] = std::make_shared<Buffer>(state, data->slot, *gbpBuffer);
state.gpu->bufferEvent->Signal();
state.logger->Debug("SetPreallocatedBuffer: Slot: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}, Size: 0x{:X}", data->slot, gbpBuffer->magic, gbpBuffer->width, gbpBuffer->height, gbpBuffer->stride, gbpBuffer->format, gbpBuffer->usage, gbpBuffer->index,gbpBuffer->nvmapId, gbpBuffer->nvmapHandle, gbpBuffer->offset, (1U << gbpBuffer->blockHeightLog2), gbpBuffer->size);
}
void BufferQueue::FreeBuffer(u32 slotNo) {
auto &slot = queue.at(slotNo);
if (waitVec.empty())
slot->status = BufferStatus::Free;
else {
auto context = waitVec.begin();
while (context != waitVec.end()) {
if (slot->resolution.width == context->input.width && slot->resolution.height == context->input.height && slot->gbpBuffer.usage == context->input.usage) {
context->thread->WakeUp();
gpu::Parcel out(state);
DequeueOut output(slotNo);
out.WriteData(output);
out.WriteParcel(context->buffer);
slot->status = BufferStatus::Dequeued;
waitVec.erase(context);
break;
}
context++;
}
}
state.logger->Debug("SetPreallocatedBuffer: Slot: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}, Size: 0x{:X}",
data->slot,
gbpBuffer->magic,
gbpBuffer->width,
gbpBuffer->height,
gbpBuffer->stride,
gbpBuffer->format,
gbpBuffer->usage,
gbpBuffer->index,
gbpBuffer->nvmapId,
gbpBuffer->nvmapHandle,
gbpBuffer->offset,
(1U << gbpBuffer->blockHeightLog2),
gbpBuffer->size);
}
}

View File

@ -13,11 +13,11 @@ namespace skyline::gpu {
u32 width; //!< The width component of the resolution
u32 height; //!< The height component of the resolution
bool operator==(const Resolution &r) {
inline bool operator==(const Resolution &r) {
return (width == r.width) && (height == r.height);
}
bool operator!=(const Resolution &r) {
inline bool operator!=(const Resolution &r) {
return !operator==(r);
}
};
@ -58,8 +58,7 @@ namespace skyline::gpu {
enum class BufferStatus {
Free,
Dequeued,
Queued,
Acquired
Queued
};
/**
@ -106,7 +105,6 @@ namespace skyline::gpu {
GbpBuffer gbpBuffer; //!< The information about the underlying buffer
BufferStatus status{BufferStatus::Free}; //!< The status of this buffer
std::vector<u8> dataBuffer; //!< The vector holding the actual pixel data
std::vector<u8> swizzBuffer; //!< The vector holding the swizzled pixel data
std::shared_ptr<device::NvMap::NvMapObject> nvBuffer{}; //!< A shared pointer to the buffer's nvmap object
/**
@ -123,55 +121,12 @@ namespace skyline::gpu {
};
/**
* @brief This holds the state of all the buffers used by the guest application
* @brief This is used to manage and queue up all display buffers to be shown
*/
class BufferQueue {
private:
const DeviceState &state; //!< The state of the device
/**
* @brief This is the input struct for DequeueBuffer
*/
struct DequeueIn {
u32 format;
u32 width;
u32 height;
u32 timestamps;
u32 usage;
};
/**
* @brief This is the output struct for DequeueBuffer
*/
struct DequeueOut {
u32 slot; //!< The slot of the dequeued buffer
u32 _unk0_;
u32 _unk1_;
u32 _unk2_[11]{};
/**
* @param slot The slot of the dequeued buffer
*/
DequeueOut(u32 slot);
};
/**
* @brief This holds the context of a thread waiting on a buffer
*/
struct WaitContext {
std::shared_ptr<kernel::type::KThread> thread; //!< The thread that is waiting on a buffer
DequeueIn input; //!< The input of DequeueBuffer
kernel::ipc::OutputBuffer buffer; //!< The output buffer to write the parcel into
/**
* @param thread The thread that is waiting on a buffer
* @param input The input of DequeueBuffer
* @param buffer The output buffer to write the parcel into
*/
WaitContext(std::shared_ptr<kernel::type::KThread> thread, DequeueIn input, kernel::ipc::OutputBuffer& buffer);
};
std::vector<WaitContext> waitVec; //!< A vector of shared pointers to threads waiting on a buffer
public:
std::unordered_map<u32, std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers
std::queue<std::shared_ptr<Buffer>> displayQueue; //!< A queue of all the buffers to be posted to the display
@ -210,6 +165,8 @@ namespace skyline::gpu {
* @brief This frees a buffer which is currently queued
* @param slotNo The slot of the buffer
*/
void FreeBuffer(u32 slotNo);
inline void FreeBuffer(u32 slotNo) {
queue.at(slotNo)->status = BufferStatus::Free;
}
};
}

View File

@ -73,7 +73,7 @@ namespace skyline::kernel::svc {
void QueryMemory(DeviceState &state) {
memory::MemoryInfo memInfo{};
u64 addr = (state.ctx->registers.x2 & ~(PAGE_SIZE - 1));
u64 addr = state.ctx->registers.x2 & ~(PAGE_SIZE - 1);
bool found = false;
for (const auto&[address, region] : state.process->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
@ -161,12 +161,9 @@ namespace skyline::kernel::svc {
case 1:
case 2:
state.logger->Debug("svcSleepThread: Yielding thread: {}", in);
state.thread->status = type::KThread::Status::Runnable; // Will cause the application to awaken on the next iteration of the main loop
break;
default:
state.logger->Debug("svcSleepThread: Thread sleeping for {} ns", in);
state.thread->timeout = GetCurrTimeNs() + in;
state.thread->status = type::KThread::Status::Sleeping;
}
}
@ -298,9 +295,9 @@ namespace skyline::kernel::svc {
return;
}
std::vector<handle_t> waitHandles(numHandles);
std::vector<std::shared_ptr<type::KSyncObject>> objectTable;
state.process->ReadMemory(waitHandles.data(), state.ctx->registers.x1, numHandles * sizeof(handle_t));
std::string handleStr;
uint index{};
for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle);
auto object = state.process->handleTable.at(handle);
@ -312,28 +309,29 @@ namespace skyline::kernel::svc {
break;
default: {
state.ctx->registers.w0 = constant::status::InvHandle;
state.thread->ClearWaitObjects();
return;
}
}
auto syncObject = std::static_pointer_cast<type::KSyncObject>(object);
if (syncObject->signalled) {
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", handle);
objectTable.push_back(std::static_pointer_cast<type::KSyncObject>(object));
}
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, state.ctx->registers.x3);
auto timeout = state.ctx->registers.x3 + GetCurrTimeNs();
while (true) {
uint index{};
for (const auto &object : objectTable) {
if (object->signalled) {
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles.at(index));
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.w1 = index;
state.thread->ClearWaitObjects();
return;
}
state.thread->waitObjects.push_back(syncObject);
syncObject->waitThreads.emplace_back(state.thread->pid, index);
index++;
}
if (GetCurrTimeNs() >= timeout) {
state.ctx->registers.w0 = constant::status::Timeout;
return;
}
}
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
if (state.ctx->registers.x3 != std::numeric_limits<u64>::max())
state.thread->timeout = GetCurrTimeNs() + timeout;
else
state.thread->timeout = 0;
state.thread->status = type::KThread::Status::WaitSync;
}
void ArbitrateLock(DeviceState &state) {
@ -365,6 +363,9 @@ namespace skyline::kernel::svc {
void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddr = state.ctx->registers.x0;
auto condAddr = state.ctx->registers.x1;
try {
auto &cvar = state.process->condVarMap.at(condAddr);
if ((mtxAddr & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: {}", mtxAddr);
@ -373,41 +374,42 @@ namespace skyline::kernel::svc {
auto handle = state.ctx->registers.w2;
if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Called from another thread");
state.process->MutexUnlock(mtxAddr);
auto condAddr = state.ctx->registers.x1;
auto &cvarVec = state.process->condVarMap[condAddr];
for (auto thread = cvarVec.begin();; thread++) {
if ((*thread)->priority < state.thread->priority) {
cvarVec.insert(thread, state.thread);
break;
} else if (thread + 1 == cvarVec.end()) {
cvarVec.push_back(state.thread);
break;
}
}
state.process->MutexLock(mtxAddr);
auto &mutex = state.process->mutexMap.at(mtxAddr);
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x:{:X}, Timeout: {} ns", mtxAddr, condAddr, timeout);
state.thread->status = type::KThread::Status::WaitCondVar;
state.thread->timeout = GetCurrTimeNs() + timeout;
timespec spec{};
clock_gettime(CLOCK_REALTIME, &spec);
u128 time = u128(spec.tv_sec * 1000000000U + spec.tv_nsec) + timeout; // u128 to prevent overflow
spec.tv_sec = static_cast<time_t>(time / 1000000000U);
spec.tv_nsec = static_cast<long>(time % 1000000000U);
if (pthread_cond_timedwait(&cvar, &mutex, &spec) == ETIMEDOUT)
state.ctx->registers.w0 = constant::status::Timeout;
else
state.ctx->registers.w0 = constant::status::Success;
state.process->MutexUnlock(mtxAddr);
} catch (const std::out_of_range &) {
state.logger->Debug("svcWaitProcessWideKeyAtomic: No Conditional-Variable at 0x{:X}", condAddr);
state.process->condVarMap[condAddr] = PTHREAD_COND_INITIALIZER;
state.ctx->registers.w0 = constant::status::Success;
}
}
void SignalProcessWideKey(DeviceState &state) {
auto address = state.ctx->registers.x0;
auto count = state.ctx->registers.w1;
state.ctx->registers.w0 = constant::status::Success;
if (!state.process->condVarMap.count(address)) {
state.logger->Debug("svcSignalProcessWideKey: No Conditional-Variable at 0x{:X}", address);
return;
}
auto &cvarVec = state.process->condVarMap.at(address);
count = std::min(count, static_cast<u32>(cvarVec.size()));
for (uint index = 0; index < count; index++)
cvarVec[index]->status = type::KThread::Status::Runnable;
cvarVec.erase(cvarVec.begin(), cvarVec.begin() + count);
if (cvarVec.empty())
state.process->condVarMap.erase(address);
auto count = state.ctx->registers.x1;
try {
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count);
auto &cvar = state.process->condVarMap.at(address);
/*
for (u32 iter = 0; iter < count; iter++)
pthread_cond_signal(&cvar);
*/
} catch (const std::out_of_range &) {
state.logger->Debug("svcSignalProcessWideKey: No Conditional-Variable at 0x{:X}", address);
state.process->condVarMap[address] = PTHREAD_COND_INITIALIZER;
}
state.ctx->registers.w0 = constant::status::Success;
}
void GetSystemTick(DeviceState &state) {

View File

@ -1,534 +0,0 @@
#include "svc.h"
#include <os.h>
namespace skyline::kernel::svc {
void SetHeapSize(DeviceState &state) {
const u32 size = state.ctx->registers.w1;
if(size%constant::HeapSizeDiv != 0) {
state.ctx->registers.x1 = 0;
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcSetHeapSize: 'size' not divisible by 2MB: {}", size);
return;
}
std::shared_ptr<type::KPrivateMemory> heap;
try {
heap = state.process->memoryRegionMap.at(memory::Region::Heap);
heap->Resize(size, true);
} catch (const exception &) {
state.logger->Warn("svcSetHeapSize: Falling back to recreating memory");
state.process->UnmapPrivateRegion(memory::Region::Heap);
heap = state.process->MapPrivateRegion(constant::HeapAddr, size, {true, true, false}, memory::Type::Heap, memory::Region::Heap).item;
}
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.x1 = heap->address;
state.logger->Debug("svcSetHeapSize: Allocated at 0x{:X} for 0x{:X} bytes", heap->address, heap->size);
}
void SetMemoryAttribute(DeviceState &state) {
const u64 addr = state.ctx->registers.x0;
if((addr & (PAGE_SIZE - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcSetMemoryAttribute: 'address' not page aligned: {}", addr);
return;
}
const u64 size = state.ctx->registers.x1;
if((size & (PAGE_SIZE - 1U)) || !size) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcSetMemoryAttribute: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
return;
}
u32 mask = state.ctx->registers.w2;
u32 value = state.ctx->registers.w3;
u32 maskedValue = mask | value;
if(maskedValue != mask) {
state.ctx->registers.w0 = constant::status::InvCombination;
state.logger->Warn("svcSetMemoryAttribute: 'mask' invalid: 0x{:X}, 0x{:X}", mask, value);
return;
}
memory::MemoryAttribute attribute = *reinterpret_cast<memory::MemoryAttribute*>(&maskedValue);
bool found = false;
for (const auto&[address, region] : state.process->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
bool subFound = false;
for (auto &subregion : region->regionInfoVec) {
if ((address >= subregion.address) && (address < (subregion.address + subregion.size)))
subregion.isUncached = attribute.isUncached;
subFound = true;
break;
}
if (!subFound)
region->regionInfoVec.emplace_back(addr, size, static_cast<bool>(attribute.isUncached));
found = true;
break;
}
}
if(!found) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcSetMemoryAttribute: Cannot find memory region: 0x{:X}", addr);
return;
}
state.logger->Debug("svcSetMemoryAttribute: Set caching to {} at 0x{:X} for 0x{:X} bytes", !attribute.isUncached, addr, size);
state.ctx->registers.w0 = constant::status::Success;
}
void QueryMemory(DeviceState &state) {
memory::MemoryInfo memInfo{};
u64 addr = (state.ctx->registers.x2 & ~(PAGE_SIZE-1));
bool found = false;
for (const auto&[address, region] : state.process->memoryMap) {
if (addr >= address && addr < (address + region->size)) {
memInfo = region->GetInfo(addr);
found = true;
break;
}
}
if (!found) {
for (const auto &object : state.process->handleTable) {
if (object.second->objectType == type::KType::KSharedMemory) {
const auto &mem = state.process->GetHandle<type::KSharedMemory>(object.first);
if (mem->guest.valid()) {
if (addr >= mem->guest.address && addr < (mem->guest.address + mem->guest.size)) {
memInfo = mem->GetInfo();
found = true;
break;
}
}
} else if (object.second->objectType == type::KType::KTransferMemory) {
const auto &mem = state.process->GetHandle<type::KTransferMemory>(object.first);
if (addr >= mem->cAddress && addr < (mem->cAddress + mem->cSize)) {
memInfo = mem->GetInfo();
found = true;
break;
}
}
}
if (!found) {
memInfo = {
.baseAddress = constant::BaseAddr,
.size = static_cast<u64>(constant::BaseEnd),
.type = static_cast<u64>(memory::Type::Unmapped)
};
state.logger->Debug("svcQueryMemory: Cannot find block of address: 0x{:X}", addr);
}
}
state.logger->Debug("svcQueryMemory: Address: 0x{:X}, Size: 0x{:X}, Type: 0x{:X}, Is Uncached: {}, Permissions: {}{}{}", memInfo.baseAddress, memInfo.size, memInfo.type, static_cast<bool>(memInfo.memoryAttribute.isUncached), memInfo.r ? "R" : "-", memInfo.w ? "W" : "-", memInfo.x ? "X" : "-");
state.process->WriteMemory<memory::MemoryInfo>(memInfo, state.ctx->registers.x0);
state.ctx->registers.w0 = constant::status::Success;
}
void ExitProcess(DeviceState &state) {
state.logger->Debug("svcExitProcess: Exiting current process: {}", state.process->pid);
state.os->KillThread(state.process->pid);
}
void CreateThread(DeviceState &state) {
u64 entryAddr = state.ctx->registers.x1;
u64 entryArg = state.ctx->registers.x2;
u64 stackTop = state.ctx->registers.x3;
u8 priority = static_cast<u8>(state.ctx->registers.w4);
if((priority < constant::PriorityNin.first) && (priority > constant::PriorityNin.second)) { // NOLINT(misc-redundant-expression)
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcCreateThread: 'priority' invalid: {}", priority);
return;
}
auto thread = state.process->CreateThread(entryAddr, entryArg, stackTop, priority);
state.logger->Debug("svcCreateThread: Created thread with handle 0x{:X} (Entry Point: 0x{:X}, Argument: 0x{:X}, Stack Pointer: 0x{:X}, Priority: {}, PID: {})", thread->handle, entryAddr, entryArg, stackTop, priority, thread->pid);
state.ctx->registers.w1 = thread->handle;
state.ctx->registers.w0 = constant::status::Success;
}
void StartThread(DeviceState &state) {
auto handle = state.ctx->registers.w0;
try {
auto thread = state.process->GetHandle<type::KThread>(handle);
state.logger->Debug("svcStartThread: Starting thread: 0x{:X}, PID: {}", handle, thread->pid);
thread->Start();
} catch (const std::exception&) {
state.logger->Warn("svcStartThread: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::InvHandle;
}
}
void ExitThread(DeviceState &state) {
state.logger->Debug("svcExitProcess: Exiting current thread: {}", state.thread->pid);
state.os->KillThread(state.thread->pid);
}
void SleepThread(DeviceState &state) {
auto in = state.ctx->registers.x0;
switch (in) {
case 0:
case 1:
case 2:
state.logger->Debug("svcSleepThread: Yielding thread: {}", in);
state.thread->status = type::KThread::Status::Runnable; // Will cause the application to awaken on the next iteration of the main loop
break;
default:
state.logger->Debug("svcSleepThread: Thread sleeping for {} ns", in);
state.thread->timeout = GetCurrTimeNs() + in;
state.thread->status = type::KThread::Status::Sleeping;
}
}
void GetThreadPriority(DeviceState &state) {
auto handle = state.ctx->registers.w0;
try {
auto priority = state.process->GetHandle<type::KThread>(handle)->priority;
state.ctx->registers.w1 = priority;
state.ctx->registers.w0 = constant::status::Success;
state.logger->Debug("svcGetThreadPriority: Writing thread priority {}", priority);
} catch (const std::exception&) {
state.logger->Warn("svcGetThreadPriority: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::InvHandle;
}
}
void SetThreadPriority(DeviceState &state) {
auto handle = state.ctx->registers.w0;
auto priority = state.ctx->registers.w1;
try {
state.process->GetHandle<type::KThread>(handle)->UpdatePriority(static_cast<u8>(priority));
state.ctx->registers.w0 = constant::status::Success;
state.logger->Debug("svcSetThreadPriority: Setting thread priority to {}", priority);
} catch (const std::exception&) {
state.logger->Warn("svcSetThreadPriority: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::InvHandle;
}
}
void MapSharedMemory(DeviceState &state) {
try {
auto object = state.process->GetHandle<type::KSharedMemory>(state.ctx->registers.w0);
u64 addr = state.ctx->registers.x1;
if ((addr & (PAGE_SIZE - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcMapSharedMemory: 'address' not page aligned: 0x{:X}", addr);
return;
}
const u64 size = state.ctx->registers.x2;
if ((size & (PAGE_SIZE - 1U)) || !size) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcMapSharedMemory: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
return;
}
u32 perm = state.ctx->registers.w3;
memory::Permission permission = *reinterpret_cast<memory::Permission *>(&perm);
if ((permission.w && !permission.r) || (permission.x && !permission.r)) {
state.logger->Warn("svcMapSharedMemory: 'permission' invalid: {}{}{}", permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
state.ctx->registers.w0 = constant::status::InvPermission;
return;
}
state.logger->Debug("svcMapSharedMemory: Mapping shared memory at 0x{:X} for {} bytes ({}{}{})", addr, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
object->Map(addr, size, permission);
state.ctx->registers.w0 = constant::status::Success;
} catch (const std::exception &) {
state.logger->Warn("svcMapSharedMemory: 'handle' invalid: 0x{:X}", state.ctx->registers.w0);
state.ctx->registers.w0 = constant::status::InvHandle;
}
}
void CreateTransferMemory(DeviceState &state) {
u64 addr = state.ctx->registers.x1;
if ((addr & (PAGE_SIZE - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcCreateTransferMemory: 'address' not page aligned: {}", addr);
return;
}
u64 size = state.ctx->registers.x2;
if ((size & (PAGE_SIZE - 1U)) || !size) {
state.ctx->registers.w0 = constant::status::InvSize;
state.logger->Warn("svcCreateTransferMemory: 'size' {}: {}", size ? "not page aligned" : "is zero", size);
return;
}
u32 perm = state.ctx->registers.w3;
memory::Permission permission = *reinterpret_cast<memory::Permission *>(&perm);
if ((permission.w && !permission.r) || (permission.x && !permission.r)) {
state.logger->Warn("svcCreateTransferMemory: 'permission' invalid: {}{}{}", permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
state.ctx->registers.w0 = constant::status::InvPermission;
return;
}
state.logger->Debug("svcCreateTransferMemory: Creating transfer memory at 0x{:X} for {} bytes ({}{}{})", addr, size, permission.r ? "R" : "-", permission.w ? "W" : "-", permission.x ? "X" : "-");
auto shmem = state.process->NewHandle<type::KTransferMemory>(state.process->pid, addr, size, permission);
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.w1 = shmem.handle;
}
void CloseHandle(DeviceState &state) {
auto handle = static_cast<handle_t>(state.ctx->registers.w0);
try {
state.process->handleTable.erase(handle);
state.logger->Debug("svcCloseHandle: Closing handle: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::Success;
} catch(const std::exception&) {
state.logger->Warn("svcCloseHandle: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::InvHandle;
}
}
void ResetSignal(DeviceState &state) {
auto handle = state.ctx->registers.w0;
try {
auto &object = state.process->handleTable.at(handle);
switch (object->objectType) {
case (type::KType::KEvent):
std::static_pointer_cast<type::KEvent>(object)->ResetSignal();
break;
case (type::KType::KProcess):
std::static_pointer_cast<type::KProcess>(object)->ResetSignal();
break;
default: {
state.logger->Warn("svcResetSignal: 'handle' type invalid: 0x{:X} ({})", handle, object->objectType);
state.ctx->registers.w0 = constant::status::InvHandle;
return;
}
}
state.logger->Debug("svcResetSignal: Resetting signal: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::Success;
} catch(const std::out_of_range&) {
state.logger->Warn("svcResetSignal: 'handle' invalid: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::InvHandle;
return;
}
}
void WaitSynchronization(DeviceState &state) {
auto numHandles = state.ctx->registers.w2;
if (numHandles > constant::MaxSyncHandles) {
state.ctx->registers.w0 = constant::status::MaxHandles;
return;
}
std::vector<handle_t> waitHandles(numHandles);
state.process->ReadMemory(waitHandles.data(), state.ctx->registers.x1, numHandles * sizeof(handle_t));
std::string handleStr;
uint index{};
for (const auto &handle : waitHandles) {
handleStr += fmt::format("* 0x{:X}\n", handle);
auto object = state.process->handleTable.at(handle);
switch (object->objectType) {
case type::KType::KProcess:
case type::KType::KThread:
case type::KType::KEvent:
case type::KType::KSession:
break;
default: {
state.ctx->registers.w0 = constant::status::InvHandle;
state.thread->ClearWaitObjects();
return;
}
}
auto syncObject = std::static_pointer_cast<type::KSyncObject>(object);
if (syncObject->signalled) {
state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", handle);
state.ctx->registers.w0 = constant::status::Success;
state.ctx->registers.w1 = index;
state.thread->ClearWaitObjects();
return;
}
state.thread->waitObjects.push_back(syncObject);
syncObject->waitThreads.emplace_back(state.thread->pid, index);
}
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitSynchronization: Waiting on handles:\n{}Timeout: 0x{:X} ns", handleStr, timeout);
if (state.ctx->registers.x3 != std::numeric_limits<u64>::max())
state.thread->timeout = GetCurrTimeNs() + timeout;
else
state.thread->timeout = 0;
state.thread->status = type::KThread::Status::WaitSync;
}
void ArbitrateLock(DeviceState &state) {
auto addr = state.ctx->registers.x1;
if((addr & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateLock: 'address' not word aligned: {}", addr);
return;
}
auto handle = state.ctx->registers.w2;
if (handle != state.thread->handle)
throw exception("svcArbitrateLock: Called from another thread");
state.logger->Debug("svcArbitrateLock: Locking mutex at 0x{:X} for thread 0x{:X}", addr, handle);
state.process->MutexLock(addr);
state.ctx->registers.w0 = constant::status::Success;
}
void ArbitrateUnlock(DeviceState &state) {
auto addr = state.ctx->registers.x0;
if((addr & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcArbitrateUnlock: 'address' not word aligned: {}", addr);
return;
}
state.logger->Debug("svcArbitrateUnlock: Unlocking mutex at 0x{:X}", addr);
state.process->MutexUnlock(addr);
state.ctx->registers.w0 = constant::status::Success;
}
void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddr = state.ctx->registers.x0;
if((mtxAddr & ((1UL << WORD_BIT) - 1U))) {
state.ctx->registers.w0 = constant::status::InvAddress;
state.logger->Warn("svcWaitProcessWideKeyAtomic: mutex address not word aligned: {}", mtxAddr);
return;
}
auto handle = state.ctx->registers.w2;
if (handle != state.thread->handle)
throw exception("svcWaitProcessWideKeyAtomic: Called from another thread");
state.process->MutexUnlock(mtxAddr);
auto condAddr = state.ctx->registers.x1;
auto &cvarVec = state.process->condVarMap[condAddr];
for (auto thread = cvarVec.begin();; thread++) {
if ((*thread)->priority < state.thread->priority) {
cvarVec.insert(thread, state.thread);
break;
} else if (thread + 1 == cvarVec.end()) {
cvarVec.push_back(state.thread);
break;
}
}
auto timeout = state.ctx->registers.x3;
state.logger->Debug("svcWaitProcessWideKeyAtomic: Mutex: 0x{:X}, Conditional-Variable: 0x:{:X}, Timeout: {} ns", mtxAddr, condAddr, timeout);
state.thread->status = type::KThread::Status::WaitCondVar;
state.thread->timeout = GetCurrTimeNs() + timeout;
state.ctx->registers.w0 = constant::status::Success;
}
void SignalProcessWideKey(DeviceState &state) {
auto address = state.ctx->registers.x0;
auto count = state.ctx->registers.w1;
state.ctx->registers.w0 = constant::status::Success;
if (!state.process->condVarMap.count(address)) {
state.logger->Debug("svcSignalProcessWideKey: No Conditional-Variable at 0x{:X}", address);
return;
}
auto &cvarVec = state.process->condVarMap.at(address);
count = std::min(count, static_cast<u32>(cvarVec.size()));
for (uint index = 0; index < count; index++)
cvarVec[index]->status = type::KThread::Status::Runnable;
cvarVec.erase(cvarVec.begin(), cvarVec.begin() + count);
if (cvarVec.empty())
state.process->condVarMap.erase(address);
state.logger->Debug("svcSignalProcessWideKey: Signalling Conditional-Variable at 0x{:X} for {}", address, count);
}
void GetSystemTick(DeviceState &state) {
u64 tick;
asm("STR X1, [SP, #-16]!\n\t"
"MRS %0, CNTVCT_EL0\n\t"
"MOV X1, #0xF800\n\t"
"MOVK X1, #0x124, lsl #16\n\t"
"MUL %0, %0, X1\n\t"
"MRS X1, CNTFRQ_EL0\n\t"
"UDIV %0, %0, X1\n\t"
"LDR X1, [SP], #16" : "=r"(tick));
state.ctx->registers.x0 = tick;
}
void ConnectToNamedPort(DeviceState &state) {
char port[constant::PortSize + 1]{0};
state.process->ReadMemory(port, state.ctx->registers.x1, constant::PortSize);
handle_t handle{};
if (std::strcmp(port, "sm:") == 0)
handle = state.os->serviceManager.NewSession(service::Service::sm);
else {
state.logger->Warn("svcConnectToNamedPort: Connecting to invalid port: '{}'", port);
state.ctx->registers.w0 = constant::status::NotFound;
return;
}
state.logger->Debug("svcConnectToNamedPort: Connecting to port '{}' at 0x{:X}", port, handle);
state.ctx->registers.w1 = handle;
state.ctx->registers.w0 = constant::status::Success;
}
void SendSyncRequest(DeviceState &state) {
state.os->serviceManager.SyncRequestHandler(static_cast<handle_t>(state.ctx->registers.x0));
state.ctx->registers.w0 = constant::status::Success;
}
void GetThreadId(DeviceState &state) {
pid_t pid{};
auto handle = state.ctx->registers.w1;
if (handle != constant::ThreadSelf) {
pid = state.process->GetHandle<type::KThread>(handle)->pid;
} else
pid = state.thread->pid;
state.logger->Debug("svcGetThreadId: Handle: 0x{:X}, PID: {}", handle, pid);
state.ctx->registers.x1 = static_cast<u64>(pid);
state.ctx->registers.w0 = constant::status::Success;
}
void OutputDebugString(DeviceState &state) {
std::string debug(state.ctx->registers.x1, '\0');
state.process->ReadMemory(debug.data(), state.ctx->registers.x0, state.ctx->registers.x1);
if(debug.back() == '\n')
debug.pop_back();
state.logger->Info("Debug Output: {}", debug);
state.ctx->registers.w0 = constant::status::Success;
}
void GetInfo(DeviceState &state) {
auto id0 = state.ctx->registers.w1;
auto handle = state.ctx->registers.w2;
auto id1 = state.ctx->registers.x3;
u64 out{};
switch (id0) {
case constant::infoState::AllowedCpuIdBitmask:
case constant::infoState::AllowedThreadPriorityMask:
case constant::infoState::IsCurrentProcessBeingDebugged:
case constant::infoState::TitleId:
case constant::infoState::PrivilegedProcessId:
break;
case constant::infoState::AliasRegionBaseAddr:
out = constant::MapAddr;
break;
case constant::infoState::AliasRegionSize:
out = constant::MapSize;
break;
case constant::infoState::HeapRegionBaseAddr:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address;
break;
case constant::infoState::HeapRegionSize:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->size;
break;
case constant::infoState::TotalMemoryAvailable:
out = constant::TotalPhyMem;
break;
case constant::infoState::TotalMemoryUsage:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address + state.process->mainThreadStackSz + state.process->GetProgramSize();
break;
case constant::infoState::AddressSpaceBaseAddr:
out = constant::BaseAddr;
break;
case constant::infoState::AddressSpaceSize:
out = constant::BaseEnd;
break;
case constant::infoState::StackRegionBaseAddr:
out = state.thread->stackTop;
break;
case constant::infoState::StackRegionSize:
out = state.process->mainThreadStackSz;
break;
case constant::infoState::PersonalMmHeapSize:
out = constant::TotalPhyMem;
break;
case constant::infoState::PersonalMmHeapUsage:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address + state.process->mainThreadStackSz;
break;
case constant::infoState::TotalMemoryAvailableWithoutMmHeap:
out = constant::TotalPhyMem; // TODO: NPDM specifies SystemResourceSize, subtract that from this
break;
case constant::infoState::TotalMemoryUsedWithoutMmHeap:
out = state.process->memoryRegionMap.at(memory::Region::Heap)->address + state.process->mainThreadStackSz; // TODO: Same as above
break;
case constant::infoState::UserExceptionContextAddr:
out = state.process->tlsPages[0]->Get(0);
break;
default:
state.logger->Warn("svcGetInfo: Unimplemented case ID0: {}, ID1: {}", id0, id1);
state.ctx->registers.w0 = constant::status::Unimpl;
return;
}
state.logger->Debug("svcGetInfo: ID0: {}, ID1: {}, Out: 0x{:X}", id0, id1, out);
state.ctx->registers.x1 = out;
state.ctx->registers.w0 = constant::status::Success;
}
}

View File

@ -110,6 +110,7 @@ namespace skyline {
* @brief Stalls a thread till a KSyncObject signals or the timeout has ended (https://switchbrew.org/wiki/SVC#svcWaitSynchronization)
*/
void WaitSynchronization(DeviceState &state);
/**
* @brief Locks a specified mutex
*/

View File

@ -13,16 +13,6 @@ namespace skyline::kernel::type {
*/
KEvent(const DeviceState &state) : KSyncObject(state, KType::KEvent) {}
/**
* @brief Signals all threads waiting on this object
*/
virtual inline void Signal() {
if (!signalled) {
KSyncObject::Signal();
signalled = true;
}
}
/**
* @brief Resets the KEvent to an unsignalled state
*/

View File

@ -9,7 +9,7 @@ namespace skyline::kernel::type {
fregs.x0 = dstAddress;
fregs.x1 = size;
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x3 = static_cast<u64>(MAP_PRIVATE | MAP_ANONYMOUS | ((dstAddress) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
fregs.x3 = static_cast<u64>(MAP_PRIVATE | MAP_ANONYMOUS | ((dstAddress) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, thread);
@ -86,6 +86,7 @@ namespace skyline::kernel::type {
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.process->pid);
}
} catch (const std::exception &) {}
} catch (const std::exception &) {
}
}
};

View File

@ -3,7 +3,6 @@
#include <os.h>
#include <fcntl.h>
#include <unistd.h>
#include <asm/unistd.h>
namespace skyline::kernel::type {
KProcess::TlsPage::TlsPage(u64 address) : address(address) {}
@ -46,13 +45,14 @@ namespace skyline::kernel::type {
state.nce->WaitThreadInit(thread);
thread->tls = GetTlsSlot();
MapPrivateRegion(constant::HeapAddr, constant::DefHeapSize, {true, true, false}, memory::Type::Heap, memory::Region::Heap);
memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC); // NOLINT(hicpp-signed-bitwise)
memFd = open(fmt::format("/proc/{}/mem", pid).c_str(), O_RDWR | O_CLOEXEC);
if (memFd == -1)
throw exception("Cannot open file descriptor to /proc/{}/mem, \"{}\"", pid, strerror(errno));
}
KProcess::~KProcess() {
close(memFd);
status = Status::Exiting;
}
/**
@ -64,7 +64,7 @@ namespace skyline::kernel::type {
}
u64 CreateThreadFunc(u64 stackTop) {
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stackTop), CLONE_THREAD | CLONE_SIGHAND | CLONE_PTRACE | CLONE_FS | CLONE_VM | CLONE_FILES | CLONE_IO, nullptr); // NOLINT(hicpp-signed-bitwise)
pid_t pid = clone(&ExecuteChild, reinterpret_cast<void *>(stackTop), CLONE_THREAD | CLONE_SIGHAND | CLONE_PTRACE | CLONE_FS | CLONE_VM | CLONE_FILES | CLONE_IO, nullptr);
return static_cast<u64>(pid);
}
@ -85,7 +85,7 @@ namespace skyline::kernel::type {
threadMap[pid] = process;
return process;
*/
return 0;
return nullptr;
}
void KProcess::ReadMemory(void *destination, u64 offset, size_t size) const {
@ -96,10 +96,6 @@ namespace skyline::kernel::type {
pwrite64(memFd, source, size, offset);
}
int KProcess::GetMemoryFd() const {
return memFd;
}
KProcess::HandleOut<KPrivateMemory> KProcess::MapPrivateRegion(u64 address, size_t size, const memory::Permission perms, const memory::Type type, const memory::Region region) {
auto mem = NewHandle<KPrivateMemory>(address, size, perms, type, threadMap.at(pid));
memoryMap[mem.item->address] = mem.item;
@ -123,44 +119,28 @@ namespace skyline::kernel::type {
}
void KProcess::MutexLock(u64 address) {
auto mtxVec = state.process->mutexMap[address];
u32 mtxVal = state.process->ReadMemory<u32>(address);
if (mtxVec.empty()) {
try {
auto mtx = mutexMap.at(address);
pthread_mutex_lock(&mtx);
u32 mtxVal = ReadMemory<u32>(address);
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | state.thread->handle;
state.process->WriteMemory(mtxVal, address);
} else {
for (auto thread = mtxVec.begin();; thread++) {
if ((*thread)->priority < state.thread->priority) {
mtxVec.insert(thread, state.thread);
break;
} else if (thread + 1 == mtxVec.end()) {
mtxVec.push_back(state.thread);
break;
}
}
state.thread->status = KThread::Status::WaitMutex;
WriteMemory(mtxVal, address);
} catch (const std::out_of_range &) {
mutexMap[address] = PTHREAD_MUTEX_INITIALIZER;
}
}
void KProcess::MutexUnlock(u64 address) {
auto mtxVec = state.process->mutexMap[address];
u32 mtxVal = state.process->ReadMemory<u32>(address);
if ((mtxVal & constant::MtxOwnerMask) != state.thread->pid)
try {
auto mtx = mutexMap.at(address);
u32 mtxVal = ReadMemory<u32>(address);
if ((mtxVal & constant::MtxOwnerMask) != state.thread->handle)
throw exception("A non-owner thread tried to release a mutex");
if (mtxVec.empty()) {
mtxVal = 0;
} else {
auto &thread = mtxVec.front();
mtxVal = (mtxVal & ~constant::MtxOwnerMask) | thread->handle;
thread->status = KThread::Status::Runnable;
mtxVec.erase(mtxVec.begin());
if (!mtxVec.empty())
mtxVal |= (~constant::MtxOwnerMask);
}
state.process->WriteMemory(mtxVal, address);
}
void KProcess::ResetSignal() {
signalled = false;
WriteMemory(mtxVal, address);
pthread_mutex_unlock(&mtx);
} catch (const std::out_of_range &) {
mutexMap[address] = PTHREAD_MUTEX_INITIALIZER;
}
}
}

View File

@ -6,6 +6,7 @@
#include "KSharedMemory.h"
#include "KSession.h"
#include "KEvent.h"
#include <condition_variable>
namespace skyline::kernel::type {
/**
@ -70,10 +71,9 @@ namespace skyline::kernel::type {
std::unordered_map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
std::unordered_map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::unordered_map<pid_t, std::shared_ptr<KThread>> threadMap; //!< A mapping from a PID to it's corresponding KThread object
std::unordered_map<u64, std::vector<std::shared_ptr<KThread>>> mutexMap; //!< A map from a mutex's address to a vector of threads waiting on it (Sorted by priority)
std::unordered_map<u64, std::vector<std::shared_ptr<KThread>>> condVarMap; //!< A map from a conditional variable's address to a vector of threads waiting on it (Sorted by priority)
std::unordered_map<u64, pthread_mutex_t> mutexMap; //!< A map from a mutex's address to a vector of threads waiting on it
std::unordered_map<u64, pthread_cond_t> condVarMap; //!< A map from a conditional variable's address to a vector of threads waiting on it
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
/**
* This is used as the output for functions that return created kernel objects
* @tparam objectClass The class of the kernel object
@ -150,12 +150,6 @@ namespace skyline::kernel::type {
*/
void WriteMemory(void *source, u64 offset, size_t size) const;
/**
* @brief Returns the FD of the memory for the process
* @return The FD of the memory for the process
*/
int GetMemoryFd() const;
/**
* @brief Map a chunk of process local memory (private memory)
* @param address The address to map to (Can be 0 if address doesn't matter)
@ -258,6 +252,8 @@ namespace skyline::kernel::type {
/**
* @brief This resets the object to an unsignalled state
*/
void ResetSignal();
inline void ResetSignal() {
signalled = false;
}
};
}

View File

@ -7,7 +7,7 @@
namespace skyline::kernel::type {
u64 MapSharedFunc(u64 address, size_t size, u64 perms, u64 fd) {
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0)); // NOLINT(hicpp-signed-bitwise)
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0));
}
KSharedMemory::KSharedMemory(const DeviceState &state, u64 address, size_t size, const memory::Permission permission, memory::Type type) : type(type), KObject(state, KType::KSharedMemory) {
@ -15,7 +15,7 @@ namespace skyline::kernel::type {
if (fd < 0)
throw exception("An error occurred while creating shared memory: {}", fd);
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_SHARED | ((address) ? MAP_FIXED : 0), static_cast<int>(fd), 0));
if (address == reinterpret_cast<u64>(MAP_FAILED)) // NOLINT(hicpp-signed-bitwise)
if (address == reinterpret_cast<u64>(MAP_FAILED))
throw exception("An occurred while mapping shared region: {}", strerror(errno));
kernel = {address, size, permission};
}
@ -25,9 +25,8 @@ namespace skyline::kernel::type {
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64>(permission.Get());
fregs.x3 = static_cast<u64>(MAP_SHARED | ((address) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
fregs.x3 = static_cast<u64>(MAP_SHARED | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(fd);
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.thread->pid);
if (fregs.x0 < 0)
@ -51,7 +50,8 @@ namespace skyline::kernel::type {
}
if (kernel.valid())
UnmapSharedFunc(kernel.address, kernel.size);
} catch (const std::exception &) {}
} catch (const std::exception &) {
}
close(fd);
}

View File

@ -1,16 +0,0 @@
#include "KSyncObject.h"
#include <os.h>
namespace skyline::kernel::type {
KSyncObject::KSyncObject(const skyline::DeviceState &state, skyline::kernel::type::KType type) : KObject(state, type) {}
KSyncObject::threadInfo::threadInfo(pid_t process, u32 index) : process(process), index(index) {}
void KSyncObject::Signal() {
for (const auto &info : waitThreads) {
state.ctx->registers.w1 = info.index;
state.process->threadMap.at(info.process)->status = KThread::Status::Runnable;
}
waitThreads.clear();
}
}

View File

@ -9,27 +9,19 @@ namespace skyline::kernel::type {
*/
class KSyncObject : public KObject {
public:
bool signalled{false}; //!< If the current object is signalled (Used by KEvent as it stays signalled till svcClearEvent or svcClearSignal is called)
/**
* @brief This holds information about a specific thread that's waiting on this object
*/
struct threadInfo {
pid_t process; //!< The PID of the waiting thread
u32 index; //!< The index of the object in the wait list
threadInfo(pid_t process, u32 index);
};
std::vector<threadInfo> waitThreads; //!< A vector of threads waiting on this object
std::atomic<bool> signalled{false}; //!< If the current object is signalled (Used as object stays signalled till the signal is consumed)
/**
* @param state The state of the device
* @param type The type of the object
*/
KSyncObject(const DeviceState &state, skyline::kernel::type::KType type);
KSyncObject(const DeviceState &state, skyline::kernel::type::KType type) : KObject(state, type) {};
/**
* @brief A function for calling when a particular KSyncObject is signalled
*/
virtual void Signal();
virtual void Signal() {
signalled = true;
}
};
}

View File

@ -25,7 +25,6 @@ namespace skyline::kernel::type {
void KThread::Kill() {
if (status != Status::Dead) {
status = Status::Dead;
kill(pid, SIGKILL);
Signal();
}
}
@ -36,30 +35,4 @@ namespace skyline::kernel::type {
if (setpriority(PRIO_PROCESS, static_cast<id_t>(pid), liPriority) == -1)
throw exception("Couldn't set process priority to {} for PID: {}", liPriority, pid);
}
void KThread::Sleep() {
if (status == Status::Running) {
status = Status::Sleeping;
timeout = 0;
}
}
void KThread::WakeUp() {
if (status == Status::Sleeping)
status = Status::Runnable;
}
void KThread::ClearWaitObjects() {
for (auto &object : waitObjects) {
auto iter = object->waitThreads.begin();
while (iter != object->waitThreads.end()) {
if (iter->process == pid) {
object->waitThreads.erase(iter);
break;
}
iter++;
}
}
waitObjects.clear();
}
}

View File

@ -17,16 +17,9 @@ namespace skyline::kernel::type {
enum class Status {
Created, //!< The thread has been created but has not been started yet
Running, //!< The thread is running currently
Sleeping, //!< The thread is sleeping due to svcSleepThread
WaitSync, //!< The thread is waiting for a KSyncObject signal
WaitMutex, //!< The thread is waiting on a Mutex
WaitCondVar, //!< The thread is waiting on a Conditional Variable
Runnable, //!< The thread is ready to run after waiting
Dead //!< The thread is dead and not running
} status = Status::Created; //!< The state of the thread
std::shared_ptr<type::KSharedMemory> ctxMemory; //!< The KSharedMemory of the shared memory allocated by the guest process TLS
std::vector<std::shared_ptr<KSyncObject>> waitObjects; //!< A vector holding the objects this thread is waiting for
u64 timeout{}; //!< The end of a timeout for svcWaitSynchronization or the end of the sleep period for svcSleepThread
handle_t handle; // The handle of the object in the handle table
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
u64 stackTop; //!< The top of the stack (Where it starts growing downwards from)
@ -62,21 +55,11 @@ namespace skyline::kernel::type {
*/
void Kill();
/**
* @brief This causes this thread to sleep indefinitely (no-op if thread is already sleeping)
*/
void Sleep();
/**
* @brief This wakes up the thread from it's sleep (no-op if thread is already awake)
*/
void WakeUp();
/**
* @brief This clears all the objects in the waitObjects vector
*/
void ClearWaitObjects();
/**
* @brief Update the priority level for the process.
* @details Set the priority of the current thread to `priority` using setpriority [https://linux.die.net/man/3/setpriority]. We rescale the priority from Nintendo scale to that of Android.

View File

@ -5,7 +5,7 @@
namespace skyline::kernel::type {
u64 MapTransferFunc(u64 address, size_t size, u64 perms) {
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0), -1, 0)); // NOLINT(hicpp-signed-bitwise)
return reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, static_cast<int>(perms), MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0), -1, 0));
}
KTransferMemory::KTransferMemory(const DeviceState &state, pid_t pid, u64 address, size_t size, const memory::Permission permission) : owner(pid), cSize(size), permission(permission), KObject(state, KType::KTransferMemory) {
@ -14,7 +14,7 @@ namespace skyline::kernel::type {
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64 >(permission.Get());
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, pid);
@ -22,28 +22,23 @@ namespace skyline::kernel::type {
throw exception("An error occurred while mapping shared region in child process");
cAddress = fregs.x0;
} else {
address = MapTransferFunc(address, size, static_cast<u64>(permission.Get()));
address = reinterpret_cast<u64>(mmap(reinterpret_cast<void *>(address), size, permission.Get(), MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0), -1, 0));
if (reinterpret_cast<void *>(address) == MAP_FAILED)
throw exception("An error occurred while mapping transfer memory in kernel");
cAddress = address;
}
}
u64 UnmapTransferFunc(u64 address, size_t size) {
return static_cast<u64>(munmap(reinterpret_cast<void *>(address), size));
}
u64 KTransferMemory::Transfer(pid_t process, u64 address, u64 size) {
if (process) {
Registers fregs{};
fregs.x0 = address;
fregs.x1 = size;
fregs.x2 = static_cast<u64 >(permission.Get());
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0)); // NOLINT(hicpp-signed-bitwise)
fregs.x3 = static_cast<u64>(MAP_ANONYMOUS | MAP_PRIVATE | ((address) ? MAP_FIXED : 0));
fregs.x4 = static_cast<u64>(-1);
fregs.x8 = __NR_mmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, process);
// state.nce->ExecuteFunction(reinterpret_cast<void *>(MapTransferFunc), fregs, process);
if (fregs.x0 < 0)
throw exception("An error occurred while mapping transfer memory in child process");
address = fregs.x0;
@ -65,11 +60,10 @@ namespace skyline::kernel::type {
fregs.x1 = size;
fregs.x8 = __NR_munmap;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, owner);
// state.nce->ExecuteFunction(reinterpret_cast<void *>(UnmapTransferFunc), fregs, owner);
if (fregs.x0 < 0)
throw exception("An error occurred while unmapping transfer memory in child process");
} else {
if (reinterpret_cast<void *>(UnmapTransferFunc(address, size)) == MAP_FAILED)
if (reinterpret_cast<void *>(munmap(reinterpret_cast<void *>(address), size)) == MAP_FAILED)
throw exception("An error occurred while unmapping transfer memory in kernel");
}
owner = process;
@ -102,8 +96,9 @@ namespace skyline::kernel::type {
fregs.x1 = cSize;
state.nce->ExecuteFunction(ThreadCall::Syscall, fregs, state.process->pid);
}
} catch (const std::exception &) {}
} catch (const std::exception &) {
}
} else
UnmapTransferFunc(cAddress, cSize);
munmap(reinterpret_cast<void *>(cAddress), cSize);
}
};

View File

@ -1,4 +1,5 @@
#include <sched.h>
#include <unistd.h>
#include "os.h"
#include "jvm.h"
#include "nce/guest.h"
@ -6,14 +7,17 @@
#include "kernel/svc.h"
extern bool Halt;
extern skyline::Mutex jniMtx;
extern skyline::GroupMutex jniMtx;
namespace skyline {
void NCE::KernelThread(pid_t thread) {
try {
state.thread = state.process->threadMap.at(thread);
state.ctx = reinterpret_cast<ThreadContext *>(state.thread->ctxMemory->guest.address);
while (!Halt) {
while (true) {
std::lock_guard jniGd(jniMtx);
if (Halt)
break;
if (state.ctx->state == ThreadState::WaitKernel) {
const u16 svc = static_cast<const u16>(state.ctx->commandId);
try {
@ -29,6 +33,7 @@ namespace skyline {
} else if (state.ctx->state == ThreadState::GuestCrash) {
state.logger->Warn("Thread with PID {} has crashed due to signal: {}", thread, strsignal(state.ctx->commandId));
ThreadTrace();
state.ctx->state = ThreadState::WaitRun;
break;
}
}
@ -37,7 +42,12 @@ namespace skyline {
} catch (...) {
state.logger->Error("An unknown exception has occurred");
}
if (thread == state.process->pid) {
jniMtx.lock(GroupMutex::Group::Group2);
state.os->KillThread(thread);
Halt = true;
jniMtx.unlock();
}
}
NCE::NCE(DeviceState &state) : state(state) {}
@ -48,15 +58,25 @@ namespace skyline {
}
void NCE::Execute() {
while (!Halt && state.os->process) {
while (true) {
std::lock_guard jniGd(jniMtx);
if (Halt)
break;
state.os->serviceManager.Loop();
state.gpu->Loop();
}
Halt = false;
jniMtx.lock(GroupMutex::Group::Group2);
Halt = true;
jniMtx.unlock();
}
void ExecuteFunctionCtx(ThreadCall call, Registers &funcRegs, ThreadContext *ctx) {
/**
* This function will not work if optimizations are enabled as ThreadContext isn't volatile
* and due to that is not read on every iteration of the while loop.
* However, making ThreadContext or parts of it volatile slows down the applications as a whole.
* So, we opted to use the hacky solution and disable optimizations for this single function.
*/
void ExecuteFunctionCtx(ThreadCall call, Registers &funcRegs, ThreadContext *ctx) __attribute__ ((optnone)) {
ctx->commandId = static_cast<u32>(call);
Registers registers = ctx->registers;
while (ctx->state != ThreadState::WaitInit && ctx->state != ThreadState::WaitKernel);
@ -72,10 +92,13 @@ namespace skyline {
}
void NCE::ExecuteFunction(ThreadCall call, Registers &funcRegs, pid_t pid) {
if (state.process->status != kernel::type::KProcess::Status::Exiting)
ExecuteFunctionCtx(call, funcRegs, reinterpret_cast<ThreadContext *>(state.process->threadMap.at(pid)->ctxMemory->kernel.address));
else
throw std::out_of_range("The KProcess object is missing");
}
void NCE::WaitThreadInit(std::shared_ptr<kernel::type::KThread> &thread) {
void NCE::WaitThreadInit(std::shared_ptr<kernel::type::KThread> &thread) __attribute__ ((optnone)) {
auto ctx = reinterpret_cast<ThreadContext *>(thread->ctxMemory->kernel.address);
while (ctx->state == ThreadState::NotReady);
}
@ -98,7 +121,7 @@ namespace skyline {
ctx = ctx ? ctx : state.ctx;
if (numHist) {
std::vector<u32> instrs(numHist);
u64 size = (sizeof(u32) * numHist);
u64 size = sizeof(u32) * numHist;
u64 offset = ctx->pc - size + (2 * sizeof(u32));
state.process->ReadMemory(instrs.data(), offset, size);
for (auto &instr : instrs) {
@ -111,17 +134,22 @@ namespace skyline {
offset += sizeof(u32);
}
}
if (ctx->faultAddress)
regStr += fmt::format("\nFault Address: 0x{:X}", ctx->faultAddress);
if (ctx->sp)
regStr += fmt::format("\nStack Pointer: 0x{:X}", ctx->sp);
for (u16 index = 0; index < constant::NumRegs - 1; index += 2) {
regStr += fmt::format("\nX{}: 0x{:X}, X{}: 0x{:X}", index, ctx->registers.regs[index], index + 1, ctx->registers.regs[index + 1]);
}
if (numHist) {
state.logger->Debug("Process Trace:{}", trace);
state.logger->Debug("Raw Instructions: 0x{}\nCPU Context:{}", raw, regStr);
state.logger->Debug("Raw Instructions: 0x{}", raw);
state.logger->Debug("CPU Context:{}", regStr);
} else
state.logger->Warn("CPU Context:{}", regStr);
state.logger->Debug("CPU Context:{}", regStr);
}
const std::array<u32, 17> cntpctEl0X0 = {
const std::array<u32, 18> cntpctEl0X0 = {
0xA9BF0BE1, // STP X1, X2, [SP, #-16]!
0x3C9F0FE0, // STR Q0, [SP, #-16]!
0x3C9F0FE1, // STR Q1, [SP, #-16]!
@ -138,10 +166,11 @@ namespace skyline {
0x9E790000, // FCVTZU X0, D0
0x3CC107E2, // LDR Q2, [SP], #16
0x3CC107E1, // LDR Q1, [SP], #16
0x3CC107E0, // LDR Q0, [SP], #16
0xA8C10BE1, // LDP X1, X2, [SP], #16
};
const std::array<u32, 17> cntpctEl0X1 = {
const std::array<u32, 18> cntpctEl0X1 = {
0xA9BF0BE0, // STP X0, X2, [SP, #-16]!
0x3C9F0FE0, // STR Q0, [SP, #-16]!
0x3C9F0FE1, // STR Q1, [SP, #-16]!
@ -158,10 +187,11 @@ namespace skyline {
0x9E790001, // FCVTZU X0, D0
0x3CC107E2, // LDR Q2, [SP], #16
0x3CC107E1, // LDR Q1, [SP], #16
0x3CC107E0, // LDR Q0, [SP], #16
0xA8C10BE0, // LDP X0, X2, [SP], #16
};
std::array<u32, 17> cntpctEl0Xn = {
std::array<u32, 18> cntpctEl0Xn = {
0xA9BF07E0, // STP X0, X1, [SP, #-16]!
0x3C9F0FE0, // STR Q0, [SP, #-16]!
0x3C9F0FE1, // STR Q1, [SP, #-16]!
@ -178,6 +208,7 @@ namespace skyline {
0x00000000, // FCVTZU Xn, D0 (Set at runtime)
0x3CC107E2, // LDR Q2, [SP], #16
0x3CC107E1, // LDR Q1, [SP], #16
0x3CC107E0, // LDR Q0, [SP], #16
0xA8C107E0, // LDP X0, X1, [SP], #16
};
@ -268,9 +299,6 @@ namespace skyline {
patch.push_back(ldrX0);
patch.push_back(bret.raw);
} else if (instrMrs->srcReg == constant::CntpctEl0) {
instr::Mrs mrs(constant::CntvctEl0, regs::X(instrMrs->destReg));
*address = mrs.raw;
/*
instr::B bjunc(offset);
if (instrMrs->destReg == 0)
offset += cntpctEl0X0.size() * sizeof(u32);
@ -294,7 +322,6 @@ namespace skyline {
patch.push_back(instr);
}
patch.push_back(bret.raw);
*/
} else if (instrMrs->srcReg == constant::CntfrqEl0) {
instr::B bjunc(offset);
auto movFreq = instr::MoveU32Reg(static_cast<regs::X>(instrMrs->destReg), constant::TegraX1Freq);
@ -311,6 +338,7 @@ namespace skyline {
offset -= sizeof(u32);
patchOffset -= sizeof(u32);
}
patch.resize(patch.size() + PAGE_SIZE - 1 & ~(PAGE_SIZE - 1), 0x0);
return patch;
}
}

View File

@ -1,6 +1,7 @@
#include <asm/siginfo.h>
#include <signal.h>
#include <common.h>
#include <csignal>
#include <cstdlib>
#include <initializer_list> // This is used implicitly
#include "guest_common.h"
#define FORCE_INLINE __attribute__((always_inline)) inline // NOLINT(cppcoreguidelines-macro-usage)
@ -95,11 +96,71 @@ namespace skyline::guest {
asm("MRS %0, TPIDR_EL0":"=r"(ctx));
ctx->pc = pc;
ctx->commandId = svc;
if (svc == 0xB) { // svcSleepThread
switch (ctx->registers.x0) {
case 0:
case 1:
case 2: {
asm("MOV X0, XZR\n\t"
"MOV X1, XZR\n\t"
"MOV X2, XZR\n\t"
"MOV X3, XZR\n\t"
"MOV X4, XZR\n\t"
"MOV X5, XZR\n\t"
"MOV X8, #124\n\t" // __NR_sched_yield
"STR LR, [SP, #-16]!\n\t"
"MOV LR, SP\n\t"
"SVC #0\n\t"
"MOV SP, LR\n\t"
"LDR LR, [SP], #16":: : "x0", "x1", "x2", "x3", "x4", "x5", "x8");
break;
}
default: {
struct timespec spec = {
.tv_sec = static_cast<time_t>(ctx->registers.x0 / 1000000000),
.tv_nsec = static_cast<long>(ctx->registers.x0 % 1000000000)
};
volatile register __unused timespec *specPtr asm("x0") = &spec;
asm("MOV X1, XZR\n\t"
"MOV X2, XZR\n\t"
"MOV X3, XZR\n\t"
"MOV X4, XZR\n\t"
"MOV X5, XZR\n\t"
"MOV X8, #101\n\t" // __NR_nanosleep
"STR LR, [SP, #-16]!\n\t"
"MOV LR, SP\n\t"
"SVC #0\n\t"
"MOV SP, LR\n\t"
"LDR LR, [SP], #16":: : "x0", "x1", "x2", "x3", "x4", "x5", "x8");
}
}
return;
} else if (svc == 0x1E) {
asm("STP X1, X2, [SP, #-16]!\n\t"
"STR Q0, [SP, #-16]!\n\t"
"STR Q1, [SP, #-16]!\n\t"
"STR Q2, [SP, #-16]!\n\t"
"MRS X1, CNTFRQ_EL0\n\t"
"MRS X2, CNTVCT_EL0\n\t"
"UCVTF D0, X0\n\t"
"MOV X1, 87411174408192\n\t"
"MOVK X1, 0x4172, LSL 48\n\t"
"FMOV D2, X1\n\t"
"UCVTF D1, X1\n\t"
"FDIV D0, D0, D2\n\t"
"FMUL D0, D0, D1\n\t"
"FCVTZU %0, D0\n\t"
"LDR Q2, [SP], #16\n\t"
"LDR Q1, [SP], #16\n\t"
"LDR Q0, [SP], #16\n\t"
"LDP X1, X2, [SP], #16"::"r"(ctx->registers.x0));
return;
}
while (true) {
ctx->state = ThreadState::WaitKernel;
while (ctx->state == ThreadState::WaitKernel);
if (ctx->state == ThreadState::WaitRun)
return;
break;
else if (ctx->state == ThreadState::WaitFunc) {
if (ctx->commandId == static_cast<u32>(ThreadCall::Syscall)) {
saveCtxStack();
@ -114,18 +175,24 @@ namespace skyline::guest {
}
}
}
ctx->state = ThreadState::Running;
}
void signalHandler(int signal, siginfo_t *info, ucontext_t *ucontext) {
volatile ThreadContext *ctx;
asm("MRS %0, TPIDR_EL0":"=r"(ctx));
for (u8 index = 0; index < constant::NumRegs; index++)
for (u8 index = 0; index < 30; index++)
ctx->registers.regs[index] = ucontext->uc_mcontext.regs[index];
ctx->pc = ucontext->uc_mcontext.pc;
ctx->commandId = static_cast<u32>(signal);
ctx->faultAddress = ucontext->uc_mcontext.fault_address;
ctx->sp = ucontext->uc_mcontext.sp;
while (true) {
ctx->state = ThreadState::GuestCrash;
if (ctx->state == ThreadState::WaitRun)
exit(0);
}
}
void entry(u64 address) {
volatile ThreadContext *ctx;

View File

@ -4,10 +4,17 @@ namespace skyline {
namespace guest {
constexpr size_t saveCtxSize = 20 * sizeof(u32);
constexpr size_t loadCtxSize = 20 * sizeof(u32);
constexpr size_t svcHandlerSize = 200 * sizeof(u32);
#ifdef NDEBUG
constexpr size_t svcHandlerSize = 150 * sizeof(u32);
#else
constexpr size_t svcHandlerSize = 250 * sizeof(u32);
#endif
void entry(u64 address);
extern "C" void saveCtx(void);
extern "C" void loadCtx(void);
void svcHandler(u64 pc, u32 svc);
}
}

View File

@ -146,5 +146,7 @@ namespace skyline {
u64 pc; //!< The program counter register on the guest
Registers registers; //!< The general purpose registers on the guest
u64 tpidrroEl0; //!< The value for TPIDRRO_EL0 for the current thread
u64 faultAddress; //!< The address a fault has occurred at during guest crash
u64 sp; //!< The current location of the stack pointer set during guest crash
};
}

View File

@ -19,7 +19,7 @@ namespace skyline::kernel {
std::shared_ptr<type::KProcess> OS::CreateProcess(u64 entry, u64 argument, size_t stackSize) {
madvise(reinterpret_cast<void *>(constant::BaseAddr), constant::BaseEnd, MADV_DONTFORK);
auto *stack = static_cast<u8 *>(mmap(nullptr, stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); // NOLINT(hicpp-signed-bitwise)
auto *stack = static_cast<u8 *>(mmap(nullptr, stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS | MAP_STACK, -1, 0));
madvise(stack, reinterpret_cast<size_t>(stack) + stackSize, MADV_DOFORK);
if (stack == MAP_FAILED)
throw exception("Failed to allocate stack memory");
@ -27,12 +27,13 @@ namespace skyline::kernel {
munmap(stack, stackSize);
throw exception("Failed to create guard pages");
}
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission(true, true, 0), memory::Type::Reserved); // NOLINT(hicpp-signed-bitwise)
auto tlsMem = std::make_shared<type::KSharedMemory>(state, 0, (sizeof(ThreadContext) + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1), memory::Permission(true, true, false), memory::Type::Reserved);
tlsMem->guest = tlsMem->kernel;
madvise(reinterpret_cast<void *>(tlsMem->guest.address), tlsMem->guest.size, MADV_DOFORK);
pid_t pid = clone(reinterpret_cast<int (*)(void *)>(&guest::entry), stack + stackSize, CLONE_FILES | CLONE_FS | CLONE_SETTLS | SIGCHLD, reinterpret_cast<void *>(entry), nullptr, reinterpret_cast<void*>(tlsMem->guest.address)); // NOLINT(hicpp-signed-bitwise)
pid_t pid = clone(reinterpret_cast<int (*)(void *)>(&guest::entry), stack + stackSize, CLONE_FILES | CLONE_FS | CLONE_SETTLS | SIGCHLD, reinterpret_cast<void *>(entry), nullptr, reinterpret_cast<void *>(tlsMem->guest.address));
if (pid == -1)
throw exception("Call to clone() has failed: {}", strerror(errno));
state.logger->Debug("Successfully created process with PID: {}", pid);
process = std::make_shared<kernel::type::KProcess>(state, pid, argument, reinterpret_cast<u64>(stack), stackSize, tlsMem);
state.logger->Debug("Successfully created process with PID: {}", pid);
return process;
@ -41,14 +42,11 @@ namespace skyline::kernel {
void OS::KillThread(pid_t pid) {
if (process->pid == pid) {
state.logger->Debug("Killing process with PID: {}", pid);
for (auto&[key, value]: process->threadMap) {
value->Kill();
}
process.reset();
for (auto &thread: process->threadMap)
thread.second->Kill();
} else {
state.logger->Debug("Killing thread with TID: {}", pid);
process->threadMap.at(pid)->Kill();
process->threadMap.erase(pid);
}
}
}