mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-10 20:07:37 +00:00
feat(baselib): debugstack takes a coroutine
debugstack([thread,] [start [, count1 [, count2]]]) returns the stack of a coroutine when one is passed first, with start defaulting to 0 (the coroutine's innermost level), as 4.3.4's Script_debugstack does. 1.12.1's debugstack (0x703760) has no thread argument. A thread fails its lua_isnumber and lua_isstring checks, so it returns the thread itself, and msg .. debugstack(co) raises "attempt to concatenate a thread value" in the coroutine error paths of WeakAuras, Questie's ThreadLib, MRT and Zygor. The engine function still builds the text, so its line format and its 12/10-level head and tail windows are unchanged, which keeps addons that parse debugstack() for their folder working. It reads levels through six calls on the state it runs on: lua_getstack (0x6FBAA0) at 0x70383F, 0x70386E, 0x703896, 0x7038AD and 0x703998, and lua_getinfo (0x6FBC70) at 0x7038CD. Those calls are redirected to read from the state the new global names for the call: the thread, or the calling state. The engine function has no callers of its own, so the global is its only way in and sets that state for every call; an error part way cannot leave a thread behind for the next call. The global removes the thread argument, inserts 0 as start when none is given, and calls the engine function directly in its own frame, so levels without a thread count exactly as before.
This commit is contained in:
+13
@@ -678,6 +678,7 @@ build instructions.
|
||||
- [`difftime(time2 [, time1])`](#difftimetime2--time1)
|
||||
- [`xpcall(f, msgh [, arg1, ...])`](#xpcallf-msgh--arg1-)
|
||||
- [`GetCallstackHeight()` / `GetErrorCallstackHeight()` / `SetErrorCallstackHeight([height])`](#getcallstackheight--geterrorcallstackheight--seterrorcallstackheightheight)
|
||||
- [`debugstack(thread [, start [, count1 [, count2]]])`](#debugstackthread--start--count1--count2)
|
||||
- [`debuglocals([level [, skipFunctionsAndUserdata]])`](#debuglocalslevel--skipfunctionsanduserdata)
|
||||
- [`collectgarbage(opt [, arg])`](#collectgarbageopt--arg)
|
||||
- [`table.wipe(t)` / `wipe(t)`](#tablewipet--wipet)
|
||||
@@ -16137,6 +16138,18 @@ end
|
||||
|
||||
Works in the world and on the login screen.
|
||||
|
||||
### `debugstack(thread [, start [, count1 [, count2]]])`
|
||||
|
||||
With a coroutine as the first argument, `debugstack` returns the call stack
|
||||
of that coroutine. `start` is 0 by default, which is the innermost level of
|
||||
the coroutine. Without a coroutine, `debugstack` works as before.
|
||||
|
||||
```lua
|
||||
local co = coroutine.create(function() coroutine.yield() end)
|
||||
coroutine.resume(co)
|
||||
print(debugstack(co))
|
||||
```
|
||||
|
||||
### `debuglocals([level [, skipFunctionsAndUserdata]])`
|
||||
|
||||
Returns the local variables and the upvalues of a call level as one string.
|
||||
|
||||
@@ -8809,6 +8809,18 @@ enum Offsets {
|
||||
// name ("" for a C closure), or null for no such upvalue or a value that
|
||||
// is not a function. (0x006F45B0 is lua_pushupvalues.)
|
||||
LUA_GET_UPVALUE = 0x006F4660,
|
||||
// `debugstack([start [, count1 [, count2]]])` — the engine's C function
|
||||
// (base-function table 0x811E28); no engine callers. It reads the stack
|
||||
// of the state it is called on through five lua_getstack calls and one
|
||||
// lua_getinfo("Snl"), and pushes its text onto that same state.
|
||||
// BaseLib::DebugStack points those six calls at a thread's state.
|
||||
FUN_SCRIPT_DEBUGSTACK = 0x00703760,
|
||||
CALL_DEBUGSTACK_GETSTACK_FIRST = 0x0070383F,
|
||||
CALL_DEBUGSTACK_GETSTACK_TAIL_PROBE = 0x0070386E,
|
||||
CALL_DEBUGSTACK_GETSTACK_TAIL_START = 0x00703896,
|
||||
CALL_DEBUGSTACK_GETSTACK_TAIL_SKIP = 0x007038AD,
|
||||
CALL_DEBUGSTACK_GETSTACK_NEXT = 0x00703998,
|
||||
CALL_DEBUGSTACK_GETINFO = 0x007038CD,
|
||||
// `luaO_chunkid(out, source, bufflen)` — `void __fastcall(char *out /*ecx*/,
|
||||
// const char *source /*edx*/, int bufflen /*stack*/)`, RET 4: the name a
|
||||
// chunk has in error messages. `=name` is copied as is, `@file` is the
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// This file is part of ClassicAPI.
|
||||
//
|
||||
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
||||
// of the GNU General Public License as published by the Free Software Foundation, either
|
||||
// version 3 of the License, or (at your option) any later version.
|
||||
//
|
||||
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
// PURPOSE. See the GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// `debugstack([thread,] [start [, count1 [, count2]]])` — the stack of a
|
||||
// coroutine, as 4.3.4's Script_debugstack takes it: with a thread first,
|
||||
// the levels are that thread's and `start` defaults to 0 (the thread's
|
||||
// innermost level) instead of 1.
|
||||
//
|
||||
// 1.12's debugstack (FUN_SCRIPT_DEBUGSTACK) has no thread argument, and a
|
||||
// thread falls through its argument checks so that it returns the thread
|
||||
// itself: `msg .. debugstack(co)` raises "attempt to concatenate a thread
|
||||
// value" in the coroutine error paths of WeakAuras, Questie's ThreadLib,
|
||||
// MRT and Zygor.
|
||||
//
|
||||
// The engine's function still does the work, so its text and its head and
|
||||
// tail windows stay exactly as they are. It reads levels through six calls
|
||||
// (five lua_getstack, one lua_getinfo), and those calls are redirected to
|
||||
// read from the state Script_debugstack names for the call: the thread, or
|
||||
// the calling state when there is none. Script_debugstack replaces the
|
||||
// global and is the engine function's only way in, so the state is set for
|
||||
// every call; an error part way cannot leave a thread behind for the next.
|
||||
//
|
||||
// Registered on both Lua states, like the base-library helpers.
|
||||
|
||||
#include "Common.h"
|
||||
#include "Game.h"
|
||||
#include "Offsets.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace BaseLib::DebugStack {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace Lua = Game::Lua;
|
||||
|
||||
// The state whose levels the engine's debugstack reads, or null for its own.
|
||||
void *g_levelsOf = nullptr;
|
||||
|
||||
int __fastcall GetStack(void *L, int level, void *ar) {
|
||||
return Lua::GetStack(g_levelsOf ? g_levelsOf : L, level, ar);
|
||||
}
|
||||
|
||||
int __fastcall GetInfo(void *L, const char *what, void *ar) {
|
||||
return Lua::GetInfo(g_levelsOf ? g_levelsOf : L, what, ar);
|
||||
}
|
||||
|
||||
int __fastcall Script_debugstack(void *L) {
|
||||
const auto engineDebugStack =
|
||||
reinterpret_cast<Lua::CFunction>(Offsets::FUN_SCRIPT_DEBUGSTACK);
|
||||
g_levelsOf = nullptr;
|
||||
if (Lua::Type(L, 1) == Lua::TYPE_THREAD) {
|
||||
g_levelsOf = Lua::ToThread(L, 1);
|
||||
Lua::Remove(L, 1);
|
||||
if (!Lua::IsNumber(L, 1)) { // start defaults to 0 for a thread
|
||||
Lua::PushNumber(L, 0);
|
||||
Lua::Insert(L, 1);
|
||||
}
|
||||
}
|
||||
const int results = engineDebugStack(L);
|
||||
g_levelsOf = nullptr;
|
||||
return results;
|
||||
}
|
||||
|
||||
void Apply() {
|
||||
const auto getStack = reinterpret_cast<const void *>(&GetStack);
|
||||
for (const uintptr_t site :
|
||||
{Offsets::CALL_DEBUGSTACK_GETSTACK_FIRST, Offsets::CALL_DEBUGSTACK_GETSTACK_TAIL_PROBE,
|
||||
Offsets::CALL_DEBUGSTACK_GETSTACK_TAIL_START, Offsets::CALL_DEBUGSTACK_GETSTACK_TAIL_SKIP,
|
||||
Offsets::CALL_DEBUGSTACK_GETSTACK_NEXT})
|
||||
Common::RedirectCall(site, Offsets::LUA_GET_STACK, getStack);
|
||||
Common::RedirectCall(Offsets::CALL_DEBUGSTACK_GETINFO, Offsets::LUA_GET_INFO,
|
||||
reinterpret_cast<const void *>(&GetInfo));
|
||||
}
|
||||
|
||||
void RegisterInGame() {
|
||||
Apply();
|
||||
Lua::RegisterGlobalFunction("debugstack", &Script_debugstack);
|
||||
}
|
||||
|
||||
void RegisterGlue() {
|
||||
Apply();
|
||||
Lua::RegisterGlueFunction("debugstack", &Script_debugstack);
|
||||
}
|
||||
|
||||
const Game::ModuleAutoRegister _autoreg{&RegisterInGame};
|
||||
const Game::GlueModuleAutoRegister _autoregGlue{&RegisterGlue};
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace BaseLib::DebugStack
|
||||
Reference in New Issue
Block a user