LuaDebug.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. using Lua.Runtime;
  2. using static Lua.Internal.OpMode;
  3. using static Lua.Internal.OpArgMask;
  4. namespace Lua.Internal;
  5. internal readonly struct LuaDebug : IDisposable
  6. {
  7. readonly LuaDebugBuffer buffer;
  8. readonly uint version;
  9. LuaDebug(LuaDebugBuffer buffer, uint version)
  10. {
  11. this.buffer = buffer;
  12. this.version = version;
  13. }
  14. public string? Name
  15. {
  16. get
  17. {
  18. CheckVersion();
  19. return buffer.Name;
  20. }
  21. }
  22. public string? NameWhat
  23. {
  24. get
  25. {
  26. CheckVersion();
  27. return buffer.NameWhat;
  28. }
  29. }
  30. public string? What
  31. {
  32. get
  33. {
  34. CheckVersion();
  35. return buffer.What;
  36. }
  37. }
  38. public string? Source
  39. {
  40. get
  41. {
  42. CheckVersion();
  43. return buffer.Source;
  44. }
  45. }
  46. public int CurrentLine
  47. {
  48. get
  49. {
  50. CheckVersion();
  51. return buffer.CurrentLine;
  52. }
  53. }
  54. public int LineDefined
  55. {
  56. get
  57. {
  58. CheckVersion();
  59. return buffer.LineDefined;
  60. }
  61. }
  62. public int LastLineDefined
  63. {
  64. get
  65. {
  66. CheckVersion();
  67. return buffer.LastLineDefined;
  68. }
  69. }
  70. public int UpValueCount
  71. {
  72. get
  73. {
  74. CheckVersion();
  75. return buffer.UpValueCount;
  76. }
  77. }
  78. public int ParameterCount
  79. {
  80. get
  81. {
  82. CheckVersion();
  83. return buffer.ParameterCount;
  84. }
  85. }
  86. public bool IsVarArg
  87. {
  88. get
  89. {
  90. CheckVersion();
  91. return buffer.IsVarArg;
  92. }
  93. }
  94. public bool IsTailCall
  95. {
  96. get
  97. {
  98. CheckVersion();
  99. return buffer.IsTailCall;
  100. }
  101. }
  102. public ReadOnlySpan<char> ShortSource
  103. {
  104. get
  105. {
  106. CheckVersion();
  107. return buffer.ShortSource.AsSpan(0, buffer.ShortSourceLength);
  108. }
  109. }
  110. public static LuaDebug Create(LuaState state, CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan<char> what, out bool isValid)
  111. {
  112. if (!state.DebugBufferPool.TryPop(out var buffer))
  113. {
  114. buffer = new(state);
  115. }
  116. isValid = buffer.GetInfo(prevFrame, frame, function, pc, what);
  117. return new(buffer, buffer.version);
  118. }
  119. public void CheckVersion()
  120. {
  121. if (buffer.version != version) ThrowObjectDisposedException();
  122. }
  123. public void Dispose()
  124. {
  125. if (buffer.version != version) ThrowObjectDisposedException();
  126. buffer.Return(version);
  127. }
  128. void ThrowObjectDisposedException()
  129. {
  130. throw new ObjectDisposedException("This has been disposed");
  131. }
  132. internal class LuaDebugBuffer(LuaState state)
  133. {
  134. internal uint version;
  135. LuaState state = state;
  136. public string? Name;
  137. public string? NameWhat;
  138. public string? What;
  139. public string? Source;
  140. public int CurrentLine;
  141. public int LineDefined;
  142. public int LastLineDefined;
  143. public int UpValueCount;
  144. public int ParameterCount;
  145. public bool IsVarArg;
  146. public bool IsTailCall;
  147. public readonly char[] ShortSource = new char[59];
  148. public int ShortSourceLength;
  149. internal void Return(uint version)
  150. {
  151. if (this.version != version) throw new ObjectDisposedException("Buffer has been modified");
  152. Name = null;
  153. NameWhat = null;
  154. What = null;
  155. Source = null;
  156. CurrentLine = 0;
  157. LineDefined = 0;
  158. LastLineDefined = 0;
  159. UpValueCount = 0;
  160. ParameterCount = 0;
  161. IsVarArg = false;
  162. IsTailCall = false;
  163. if (version < uint.MaxValue)
  164. {
  165. this.version++;
  166. state.DebugBufferPool.Push(this);
  167. }
  168. }
  169. internal bool GetInfo(CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan<char> what)
  170. {
  171. LuaClosure? closure = function as LuaClosure;
  172. int status = 1;
  173. foreach (var c in what)
  174. {
  175. switch (c)
  176. {
  177. case 'S':
  178. {
  179. GetFuncInfo(function);
  180. break;
  181. }
  182. case 'l':
  183. {
  184. CurrentLine = (pc >= 0 && closure is not null) ? closure.Proto.LineInfo[pc] : -1;
  185. break;
  186. }
  187. case 'u':
  188. {
  189. UpValueCount = (closure is null) ? 0 : closure.UpValues.Length;
  190. if (closure is null)
  191. {
  192. IsVarArg = true;
  193. ParameterCount = 0;
  194. }
  195. else
  196. {
  197. IsVarArg = closure.Proto.HasVariableArguments;
  198. ParameterCount = closure.Proto.ParameterCount;
  199. }
  200. break;
  201. }
  202. case 't':
  203. {
  204. IsTailCall = frame.HasValue && (frame.Value.Flags | CallStackFrameFlags.TailCall) == frame.Value.Flags;
  205. break;
  206. }
  207. case 'n':
  208. {
  209. /* calling function is a known Lua function? */
  210. if (prevFrame is { Function: LuaClosure prevFrameClosure })
  211. NameWhat = GetFuncName(prevFrameClosure.Proto, frame?.CallerInstructionIndex ?? 0, out Name);
  212. else
  213. NameWhat = null;
  214. if (NameWhat is null)
  215. {
  216. NameWhat = ""; /* not found */
  217. Name = null;
  218. }
  219. else if (NameWhat != null && Name is "?")
  220. {
  221. Name = function.Name;
  222. }
  223. break;
  224. }
  225. case 'L':
  226. case 'f': /* handled by lua_getinfo */
  227. break;
  228. default:
  229. status = 0; /* invalid option */
  230. break;
  231. }
  232. }
  233. return status == 1;
  234. }
  235. void GetFuncInfo(LuaFunction f)
  236. {
  237. if (f is not LuaClosure cl)
  238. {
  239. Source = "=[C#]";
  240. LineDefined = -1;
  241. LastLineDefined = -1;
  242. What = "C#";
  243. }
  244. else
  245. {
  246. var p = cl.Proto;
  247. Source = p.ChunkName;
  248. LineDefined = p.LineDefined;
  249. LastLineDefined = p.LastLineDefined;
  250. What = (LineDefined == 0) ? "main" : "Lua";
  251. }
  252. ShortSourceLength = WriteShortSource(Source, ShortSource);
  253. }
  254. }
  255. internal static string? GetLocalName(Prototype prototype, int register, int pc)
  256. {
  257. var locals = prototype.LocalVariables;
  258. var localId = register + 1;
  259. foreach (var l in locals)
  260. {
  261. if (pc < l.StartPc) break;
  262. if (l.EndPc <= pc) continue;
  263. localId--;
  264. if (localId == 0)
  265. {
  266. return l.Name;
  267. }
  268. }
  269. return null;
  270. }
  271. static int FilterPc(int pc, int jmpTarget)
  272. {
  273. if (pc < jmpTarget) /* is code conditional (inside a jump)? */
  274. return -1; /* cannot know who sets that register */
  275. else return pc; /* current position sets that register */
  276. }
  277. internal static int FindSetRegister(Prototype prototype, int lastPc, int reg)
  278. {
  279. int pc;
  280. int setReg = -1; /* keep last instruction that changed 'reg' */
  281. int jmpTarget = 0; /* any code before this address is conditional */
  282. var instructions = prototype.Code;
  283. for (pc = 0; pc < lastPc; pc++)
  284. {
  285. Instruction i = instructions[pc];
  286. OpCode op = i.OpCode;
  287. int a = i.A;
  288. switch (op)
  289. {
  290. case OpCode.LoadNil:
  291. {
  292. int b = i.B;
  293. if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */
  294. setReg = FilterPc(pc, jmpTarget);
  295. break;
  296. }
  297. case OpCode.TForCall:
  298. {
  299. if (reg >= a + 2) /* affect all regs above its base */
  300. setReg = FilterPc(pc, jmpTarget);
  301. break;
  302. }
  303. case OpCode.Call:
  304. case OpCode.TailCall:
  305. {
  306. if (reg >= a) /* affect all registers above base */
  307. setReg = FilterPc(pc, jmpTarget);
  308. break;
  309. }
  310. case OpCode.Jmp:
  311. {
  312. int b = i.SBx;
  313. int dest = pc + 1 + b;
  314. /* jump is forward and do not skip `lastpc'? */
  315. if (pc < dest && dest <= lastPc)
  316. {
  317. if (dest > jmpTarget)
  318. jmpTarget = dest; /* update 'jmptarget' */
  319. }
  320. break;
  321. }
  322. case OpCode.Test:
  323. {
  324. if (reg == a) /* jumped code can change 'a' */
  325. setReg = FilterPc(pc, jmpTarget);
  326. break;
  327. }
  328. default:
  329. if (TestAMode(op) && reg == a) /* any instruction that set A */
  330. setReg = FilterPc(pc, jmpTarget);
  331. break;
  332. }
  333. }
  334. return setReg;
  335. }
  336. static void GetConstantName(Prototype p, int pc, int c, out string name)
  337. {
  338. if (c >= 256)
  339. {
  340. /* is 'c' a constant? */
  341. var kvalue = p.Constants[c - 256];
  342. if (kvalue.TryReadString(out name))
  343. {
  344. /* literal constant? */
  345. /* it is its own name */
  346. return;
  347. }
  348. /* else no reasonable name found */
  349. }
  350. else
  351. {
  352. /* 'c' is a register */
  353. var what = GetName(p, pc, c, out name!); /* search for 'c' */
  354. if (what != null && what[0] == 'c')
  355. {
  356. /* found a constant name? */
  357. return; /* 'name' already filled */
  358. }
  359. /* else no reasonable name found */
  360. }
  361. name = "?"; /* no reasonable name found */
  362. }
  363. internal static string? GetName(Prototype prototype, int lastPc, int reg, out string? name)
  364. {
  365. name = GetLocalName(prototype, reg, lastPc);
  366. if (name != null)
  367. {
  368. return "local";
  369. }
  370. var pc = FindSetRegister(prototype, lastPc, reg);
  371. if (pc != -1)
  372. {
  373. /* could find instruction? */
  374. Instruction i = prototype.Code[pc];
  375. OpCode op = i.OpCode;
  376. switch (op)
  377. {
  378. case OpCode.Move:
  379. {
  380. int b = i.B; /* move from 'b' to 'a' */
  381. if (b < i.A)
  382. return GetName(prototype, pc, b, out name); /* get name for 'b' */
  383. break;
  384. }
  385. case OpCode.GetTabUp:
  386. case OpCode.GetTable:
  387. {
  388. int k = i.C; /* key index */
  389. int t = i.B; /* table index */
  390. var vn = (op == OpCode.GetTable) /* name of indexed variable */
  391. ? GetLocalName(prototype, t + 1, pc)
  392. : prototype.UpValues[t].Name.ToString();
  393. GetConstantName(prototype, pc, k, out name);
  394. return vn is "_ENV" ? "global" : "field";
  395. }
  396. case OpCode.GetUpVal:
  397. {
  398. name = prototype.UpValues[i.B].Name.ToString();
  399. return "upvalue";
  400. }
  401. case OpCode.LoadK:
  402. case OpCode.LoadKX:
  403. {
  404. int b = (op == OpCode.LoadKX)
  405. ? i.Bx
  406. : (prototype.Code[pc + 1].Ax);
  407. if (prototype.Constants[b].TryReadString(out name))
  408. {
  409. return "constant";
  410. }
  411. break;
  412. }
  413. case OpCode.Self:
  414. {
  415. int k = i.C; /* key index */
  416. GetConstantName(prototype, pc, k, out name);
  417. return "method";
  418. }
  419. default: break; /* go through to return NULL */
  420. }
  421. }
  422. return null; /* could not find reasonable name */
  423. }
  424. internal static string? GetFuncName(Prototype prototype, int pc, out string? name)
  425. {
  426. Instruction i = prototype.Code[pc]; /* calling instruction */
  427. switch (i.OpCode)
  428. {
  429. case OpCode.Call:
  430. case OpCode.TailCall: /* get function name */
  431. return GetName(prototype, pc, i.A, out name);
  432. case OpCode.TForCall:
  433. {
  434. /* for iterator */
  435. name = "for iterator";
  436. return "for iterator";
  437. }
  438. case OpCode.Self:
  439. case OpCode.GetTabUp:
  440. case OpCode.GetTable:
  441. name = "index";
  442. break;
  443. case OpCode.SetTabUp:
  444. case OpCode.SetTable:
  445. name = "newindex";
  446. break;
  447. case OpCode.Add:
  448. name = "add";
  449. break;
  450. case OpCode.Sub:
  451. name = "sub";
  452. break;
  453. case OpCode.Mul:
  454. name = "mul";
  455. break;
  456. case OpCode.Div:
  457. name = "div";
  458. break;
  459. case OpCode.Mod:
  460. name = "mod";
  461. break;
  462. case OpCode.Pow:
  463. name = "pow";
  464. break;
  465. case OpCode.Unm:
  466. name = "unm";
  467. break;
  468. case OpCode.Len:
  469. name = "len";
  470. break;
  471. case OpCode.Concat:
  472. name = "concat";
  473. break;
  474. case OpCode.Eq:
  475. name = "eq";
  476. break;
  477. case OpCode.Lt:
  478. name = "lt";
  479. break;
  480. case OpCode.Le:
  481. name = "le";
  482. break;
  483. default:
  484. name = null;
  485. return null;
  486. }
  487. return "metamethod";
  488. }
  489. internal static int WriteShortSource(ReadOnlySpan<char> source, Span<char> dest)
  490. {
  491. const string PRE = "[string \"";
  492. const int PRE_LEN = 9;
  493. const string POS = "\"]";
  494. const int POS_LEN = 2;
  495. const string RETS = "...";
  496. const int RETS_LEN = 3;
  497. const string PREPOS = "[string \"\"]";
  498. const int BUFFER_LEN = 59;
  499. if (dest.Length != BUFFER_LEN) throw new ArgumentException("dest must be 60 chars long");
  500. if (source.Length == 0)
  501. {
  502. PREPOS.AsSpan().CopyTo(dest);
  503. return PREPOS.Length;
  504. }
  505. if (source[0] == '=')
  506. {
  507. source = source[1..]; /* skip the '=' */
  508. /* 'literal' source */
  509. if (source.Length < BUFFER_LEN) /* small enough? */
  510. {
  511. source.CopyTo(dest);
  512. return source.Length;
  513. }
  514. else
  515. {
  516. /* truncate it */
  517. source[..BUFFER_LEN].CopyTo(dest);
  518. return BUFFER_LEN;
  519. }
  520. }
  521. else if (source[0] == '@')
  522. {
  523. /* file name */
  524. source = source[1..]; /* skip the '@' */
  525. if (source.Length <= BUFFER_LEN) /* small enough? */
  526. {
  527. source.CopyTo(dest);
  528. return source.Length;
  529. }
  530. else
  531. {
  532. /* add '...' before rest of name */
  533. RETS.AsSpan().CopyTo(dest);
  534. source[^(BUFFER_LEN - RETS_LEN)..].CopyTo(dest[RETS_LEN..]);
  535. return BUFFER_LEN;
  536. }
  537. }
  538. else
  539. {
  540. /* string; format as [string "source"] */
  541. PRE.AsSpan().CopyTo(dest);
  542. int newLine = source.IndexOf('\n');
  543. if (newLine == -1 && source.Length < BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN))
  544. {
  545. source.CopyTo(dest[PRE_LEN..]);
  546. POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]);
  547. return PRE_LEN + source.Length + POS_LEN;
  548. }
  549. if (newLine != -1)
  550. {
  551. source = source[..newLine]; /* stop at first newline */
  552. }
  553. if (BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN) < source.Length)
  554. {
  555. source = source[..(BUFFER_LEN - PRE_LEN - RETS_LEN - POS_LEN)];
  556. }
  557. /* add '...' before rest of name */
  558. source.CopyTo(dest[PRE_LEN..]);
  559. RETS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]);
  560. POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length + RETS_LEN)..]);
  561. return PRE_LEN + source.Length + RETS_LEN + POS_LEN;
  562. }
  563. }
  564. static int GetOpMode(byte t, byte a, OpArgMask b, OpArgMask c, OpMode m) => (((t) << 7) | ((a) << 6) | (((byte)b) << 4) | (((byte)c) << 2) | ((byte)m));
  565. static readonly int[] OpModes =
  566. [
  567. GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_MOVE */
  568. GetOpMode(0, 1, OpArgK, OpArgN, iABx), /* OP_LOADK */
  569. GetOpMode(0, 1, OpArgN, OpArgN, iABx), /* OP_LOADKX */
  570. GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_LOADBOOL */
  571. GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_LOADNIL */
  572. GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_GETUPVAL */
  573. GetOpMode(0, 1, OpArgU, OpArgK, iABC), /* OP_GETTABUP */
  574. GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_GETTABLE */
  575. GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABUP */
  576. GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_SETUPVAL */
  577. GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABLE */
  578. GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_NEWTABLE */
  579. GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_SELF */
  580. GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_ADD */
  581. GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_SUB */
  582. GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MUL */
  583. GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_DIV */
  584. GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MOD */
  585. GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_POW */
  586. GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_UNM */
  587. GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_NOT */
  588. GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_LEN */
  589. GetOpMode(0, 1, OpArgR, OpArgR, iABC), /* OP_CONCAT */
  590. GetOpMode(0, 0, OpArgR, OpArgN, iAsBx), /* OP_JMP */
  591. GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_EQ */
  592. GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LT */
  593. GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LE */
  594. GetOpMode(1, 0, OpArgN, OpArgU, iABC), /* OP_TEST */
  595. GetOpMode(1, 1, OpArgR, OpArgU, iABC), /* OP_TESTSET */
  596. GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_CALL */
  597. GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_TAILCALL */
  598. GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_RETURN */
  599. GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORLOOP */
  600. GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORPREP */
  601. GetOpMode(0, 0, OpArgN, OpArgU, iABC), /* OP_TFORCALL */
  602. GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_TFORLOOP */
  603. GetOpMode(0, 0, OpArgU, OpArgU, iABC), /* OP_SETLIST */
  604. GetOpMode(0, 1, OpArgU, OpArgN, iABx), /* OP_CLOSURE */
  605. GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_VARARG */
  606. GetOpMode(0, 0, OpArgU, OpArgU, iAx), /* OP_EXTRAARG */
  607. ];
  608. internal static OpMode GetOpMode(OpCode m) => (OpMode)(OpModes[(int)m] & 3);
  609. internal static OpArgMask GetBMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 4) & 3);
  610. internal static OpArgMask GetCMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 2) & 3);
  611. internal static bool TestAMode(OpCode m) => (OpModes[(int)m] & (1 << 6)) != 0;
  612. internal static bool TestTMode(OpCode m) => (OpModes[(int)m] & (1 << 7)) != 0;
  613. }
  614. internal enum OpMode : byte
  615. {
  616. iABC,
  617. iABx,
  618. iAsBx,
  619. iAx
  620. }
  621. internal enum OpArgMask : byte
  622. {
  623. OpArgN, /* argument is not used */
  624. OpArgU, /* argument is used */
  625. OpArgR, /* argument is a register or a jump offset */
  626. OpArgK /* argument is a constant or register/constant */
  627. }