Lib
QOLを高める
script_debugger.cpp
Go to the documentation of this file.
1 #include "stdafx.h"
3 #include "include/script.h"
4 #include "include/debug.h"
5 #include "include/exceptions.h"
6 
7 #define ENABLE_LUAIMPL_DEPENDENT
8 
9 #ifdef ENABLE_LUAIMPL_DEPENDENT
10 extern "C" {
11 #include <lobject.h>
12 #include <lstate.h>
13 #include <ldebug.h>
14 }
15 #endif
16 
17 namespace yappy {
18 namespace lua {
19 namespace debugger {
20 
21 namespace {
22 
23 struct ExtraSpace {
24  LuaDebugger *dbg;
25 };
26 static_assert(sizeof(ExtraSpace) <= LUA_EXTRASPACE,
27  "lua extra space is not enough");
28 
29 inline ExtraSpace &extra(lua_State *L)
30 {
31  void *extra = lua_getextraspace(L);
32  return *static_cast<ExtraSpace *>(extra);
33 }
34 
35 #ifdef ENABLE_LUAIMPL_DEPENDENT
36 namespace impldep {
37 
38 inline Proto *toproto(lua_State *L, int i)
39 {
40  return getproto(L->top + (i));
41 }
42 
43 template <class F>
44 void validLines(Proto *f, F callback)
45 {
46  int n = f->sizecode;
47  for (int pc = 0; pc < n; pc++) {
48  int line = getfuncline(f, pc);
49  callback(line);
50  }
51  for (int i = 0; i < f->sizep; i++) {
52  validLines(f->p[i], callback);
53  }
54 }
55 
56 template <class F>
57 void forAllValidLines(lua_State *L, F callback)
58 {
59  Proto *f = toproto(L, -1);
60  validLines(f, callback);
61 }
62 
63 }
64 #endif
65 
66 } // namespace
67 
68 LuaDebugger::LuaDebugger(lua_State *L, bool debugEnable, int instLimit, size_t heapSize) :
69  m_L(L), m_debugEnable(debugEnable), m_instLimit(instLimit), m_heapSize(heapSize)
70 {
71  extra(L).dbg = this;
72 }
73 
74 lua_State *LuaDebugger::getLuaState() const
75 {
76  return m_L;
77 }
78 
79 void LuaDebugger::loadDebugInfo(const char *name, const char *src, size_t size)
80 {
81  lua_State *L = m_L;
82  ChunkDebugInfo info;
83  info.chunkName = name;
84 
85  // split src to lines
86  std::string srcStr(src, size);
87  std::regex re("([^\\r\\n]*)\\r?\\n?");
88  std::smatch m;
89  std::sregex_iterator iter(srcStr.cbegin(), srcStr.cend(), re);
90  std::sregex_iterator end;
91  // replace tab
92  std::regex tabspace("\\t");
93  for (; iter != end; ++iter) {
94  // 0: all, 1: ([^\r\n]*)
95  auto result = std::regex_replace(iter->str(1), tabspace, " ");
96  info.srcLines.emplace_back(result);
97  }
98 
99  // get valid lines
100  info.validLines.resize(info.srcLines.size(), 0);
101  if (!lua_isfunction(L, -1)) {
102  throw std::logic_error("stack top must be chunk");
103  }
104 #ifdef ENABLE_LUAIMPL_DEPENDENT
105  impldep::forAllValidLines(L, [&info](int line) {
106  line--;
107  if (line >= 0 && static_cast<size_t>(line) < info.validLines.size()) {
108  info.validLines[line] = 1;
109  }
110  });
111 #endif
112 
113  // breakpoints (all false)
114  info.breakPoints.resize(info.srcLines.size(), 0);
115 
116  m_debugInfo.emplace(name, std::move(info));
117 }
118 
119 void LuaDebugger::pcall(int narg, int nret, bool autoBreak)
120 {
121  lua_State *L = m_L;
122 
123  // switch hook condition by debugEnable
124  // reset "count" with instLimit for each pcall()
125  // MEMO: coroutine's lua_State has separated mask and count
126  // This is
127  int mask = m_debugEnable ?
128  (LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT) :
129  LUA_MASKCOUNT;
130  lua_sethook(L, hookRaw, mask, m_instLimit);
131  {
132  // break at the first line?
133  m_debugState = autoBreak ?
134  DebugState::BREAK_LINE_ANY : DebugState::CONT;
135  int base = lua_gettop(L) - narg; // function index
136  lua_pushcfunction(L, msghandler); // push message handler
137  lua_insert(L, base); // put it under function and args
138  int ret = lua_pcall(L, narg, nret, base);
139  lua_remove(L, base); // remove message handler
140  if (ret != LUA_OK) {
141  throw LuaError("Call global function failed", L);
142  }
143  } // unhook
144 }
145 
146 // called when lua_error occurred
147 // (* Lua call stack is not unwinded yet *)
148 // return errmsg + backtrace
149 // ref: lua.c
150 int LuaDebugger::msghandler(lua_State *L)
151 {
152  const char *msg = lua_tostring(L, 1);
153  luaL_traceback(L, L, msg, 1); /* append a standard traceback */
154 
155  // enter debugger if debugEnable
156  int origTop = lua_gettop(L);
157  LuaDebugger *dbg = extra(L).dbg;
158  if (dbg->m_debugEnable) {
160  debug::writeLine(L"[LuaDbg] ***** Lua error occurred *****");
161  debug::writeLine(lua_tostring(L, -1));
162  debug::writeLine(L"[LuaDbg] Check \"bt\" and \"fr <callstack>\"");
163  debug::writeLine(L"[LuaDbg] \"help\" command for usage");
164  dbg->cmdLoop();
165  }
166  lua_settop(L, origTop);
167  return 1; /* return the traceback */
168 }
169 
170 // static raw callback => non-static hook()
171 void LuaDebugger::hookRaw(lua_State *L, lua_Debug *ar)
172 {
173  extra(L).dbg->hook(ar);
174 }
175 
176 // switch to hookDebug() or hookNonDebug()
177 void LuaDebugger::hook(lua_Debug *ar)
178 {
179  if (m_debugEnable) {
180  hookDebug(ar);
181  }
182  else {
183  hookNonDebug(ar);
184  }
185 }
186 
187 // non-debug mode main
188 void LuaDebugger::hookNonDebug(lua_Debug *ar)
189 {
190  lua_State *L = m_L;
191  switch (ar->event) {
192  case LUA_HOOKCOUNT:
193  luaL_error(L, "Instruction count exceeded");
194  break;
195  default:
196  ASSERT(false);
197  }
198 }
199 
202 // Debugger
205 namespace {
206 
207 struct CmdEntry {
208  const wchar_t *cmd;
209  bool (LuaDebugger::*exec)(const wchar_t *usage, const std::vector<std::wstring> &argv);
210  const wchar_t *usage;
211  const wchar_t *brief;
212  const wchar_t *description;
213 };
214 
215 const CmdEntry CmdList[] = {
216  {
217  L"help", &LuaDebugger::help,
218  L"help [<cmd>...]", L"コマンドリスト、コマンド詳細説明表示",
219  L"コマンド一覧を表示します。引数にコマンド名を指定すると詳細な説明を表示します。"
220  },
221  {
222  L"conf", &LuaDebugger::conf,
223  L"conf", L"Lua バージョンとコンパイル時設定表示",
224  L"Luaのバージョンとコンパイル時設定を表示します。"
225  },
226  {
227  L"mem", &LuaDebugger::mem,
228  L"mem [-gc]", L"メモリ使用状況表示",
229  L"現在のメモリ使用量を表示します。-gc をつけると full GC を実行します。"
230  },
231  {
232  L"bt", &LuaDebugger::bt,
233  L"bt", L"バックトレース(コールスタック)表示",
234  L"バックトレース(関数呼び出し履歴)を表示します。"
235  },
236  {
237  L"fr", &LuaDebugger::fr,
238  L"fr [<frame_no>]", L"カレントフレームの変更、カレントフレームの情報表示",
239  L"コールスタックのフレーム番号を指定してそのフレームに移動します。"
240  "番号を指定しない場合、現在のフレームの詳細な情報を表示します。"
241  },
242  {
243  L"eval", &LuaDebugger::eval,
244  L"eval <lua_script>", L"Lua スクリプトの実行",
245  L"引数を連結して Lua スクリプトとしてカレントフレーム上にいるかのように実行します。"
246  },
247  {
248  L"watch", &LuaDebugger::watch,
249  L"watch [<lua_script> | -d <number>]", L"ウォッチ式の登録",
250  L"ブレークする度に自動で評価されるスクリプトを登録します。"
251  },
252  {
253  L"src", &LuaDebugger::src,
254  L"src [-f <file>] [<line>] [-n <numlines>] [-all]", L"ソースファイルの表示",
255  L"デバッガがロードしているソースファイルの情報を表示します。"
256  L"ブレーク可能ポイントや設置済みのブレークポイントも一緒に表示します。"
257  },
258  {
259  L"c", &LuaDebugger::c,
260  L"c", L"続行(continue)",
261  L"実行を続行します。"
262  },
263  {
264  L"s", &LuaDebugger::s,
265  L"s", L"ステップイン(step)",
266  L"新たな行に到達するまで実行します。関数呼び出しがあった場合、その中に入ります。"
267  },
268  {
269  L"n", &LuaDebugger::n,
270  L"n", L"ステップオーバー(next)",
271  L"新たな行に到達するまで実行します。関数呼び出しがあった場合、それが終わるまで実行します。"
272  },
273  {
274  L"out", &LuaDebugger::out,
275  L"out", L"ステップアウト",
276  L"現在の関数から戻るまで実行します。"
277  },
278  {
279  L"bp", &LuaDebugger::bp,
280  L"bp [-d] [-f <filename>] [<line>...]", L"ブレークポイントの設置/削除/表示",
281  L"ブレークポイントを設定します。引数を指定しない場合、ブレークポイント一覧を表示します。"
282  L"-d を指定すると削除します。"
283  },
284 };
285 
286 const CmdEntry *searchCommandList(const std::wstring &cmd)
287 {
288  for (const auto &entry : CmdList) {
289  if (cmd == entry.cmd) {
290  return &entry;
291  }
292  }
293  return nullptr;
294 }
295 
296 std::vector<std::wstring> readLine()
297 {
298  HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
299  error::checkWin32Result(hStdIn != INVALID_HANDLE_VALUE, "GetStdHandle() failed");
300 
301  wchar_t buf[1024];
302  DWORD readSize = 0;
303  BOOL ret = ::ReadConsole(hStdIn, buf, sizeof(buf), &readSize, nullptr);
304  error::checkWin32Result(ret != 0, "ReadConsole() failed");
305  std::wstring line(buf, readSize);
306 
307  // split the line
308  std::wregex re(L"\\S+");
309  std::wsregex_iterator iter(line.cbegin(), line.cend(), re);
310  std::wsregex_iterator end;
311  std::vector<std::wstring> result;
312  for (; iter != end; ++iter)
313  {
314  result.emplace_back(iter->str());
315  }
316  return result;
317 }
318 
319 inline int stoi_s(const std::wstring &str, int base = 10)
320 {
321  size_t idx = 0;
322  int ret = std::stoi(str, &idx, base);
323  if (idx != str.size()) {
324  throw std::invalid_argument("invalid stoi argument");
325  }
326  return ret;
327 }
328 
329 } // namespace
330 
331 // debug mode hook main
332 void LuaDebugger::hookDebug(lua_Debug *ar)
333 {
334  lua_State *L = m_L;
335  bool brk = false;
336  switch (ar->event) {
337  case LUA_HOOKCALL:
338  m_callDepth++;
339  break;
340  case LUA_HOOKTAILCALL:
341  break;
342  case LUA_HOOKRET:
343  m_callDepth--;
344  break;
345  case LUA_HOOKLINE:
346  if (m_debugState == DebugState::BREAK_LINE_ANY) {
347  brk = true;
348  break;
349  }
350  if (m_debugState == DebugState::BREAK_LINE_DEPTH0 && m_callDepth == 0) {
351  brk = true;
352  break;
353  }
354  // search breakpoints
355  {
356  lua_getinfo(L, "Sl", ar);
357  // avoid frequent new at every line event
358  m_fileNameStr = ar->source;
359  const auto &kv = m_debugInfo.find(m_fileNameStr);
360  if (kv != m_debugInfo.end()) {
361  const auto &bps = kv->second.breakPoints;
362  int ind = ar->currentline - 1;
363  if (ind >= 0 && static_cast<size_t>(ind) < bps.size() && bps[ind]) {
364  brk = true;
365  break;
366  }
367  }
368  }
369  break;
370  case LUA_HOOKCOUNT:
371  luaL_error(L, "Instruction count exceeded");
372  break;
373  default:
374  ASSERT(false);
375  }
376  if (brk) {
377  // set call stack top
378  m_currentFrame = 0;
380  debug::writeLine(L"[LuaDbg] ***** break *****");
381  summaryOnBreak(ar);
382  printWatchList();
383  debug::writeLine(L"[LuaDbg] \"help\" command for usage");
384  cmdLoop();
385  }
386 }
387 
388 void LuaDebugger::cmdLoop()
389 {
390  try {
391  while (1) {
392  debug::write(L"[LuaDbg] > ");
393  std::vector<std::wstring> argv = readLine();
394  if (argv.empty()) {
395  continue;
396  }
397  const CmdEntry *entry = searchCommandList(argv[0]);
398  if (entry == nullptr) {
399  debug::writef(L"[LuaDbg] Command not found: %s\n", argv[0].c_str());
400  continue;
401  }
402  argv.erase(argv.begin());
403  if ((this->*(entry->exec))(entry->usage, argv)) {
404  break;
405  }
407  }
408  }
409  catch (error::Win32Error &) {
410  // not AllocConsole()-ed?
411  debug::writeLine(L"Lua Debugger cannot read from stdin.");
412  debug::writeLine(L"continue...");
413  }
414 }
415 
417 // Helpers
419 
420 void LuaDebugger::summaryOnBreak(lua_Debug *ar)
421 {
422  lua_State *L = m_L;
423  lua_getinfo(L, "nSltu", ar);
424  const char *name = (ar->name == nullptr) ? "?" : ar->name;
425  debug::writef("%s (%s) %s:%d",
426  name, ar->what, ar->source, ar->currentline);
427  printSrcLines(ar->source, ar->currentline, DefSrcLines, ar->currentline);
428 }
429 
430 void LuaDebugger::printSrcLines(const std::string &name,
431  int line, int range, int execLine)
432 {
433  // find info.chunkName == name
434  const auto &kv = m_debugInfo.find(name);
435  if (kv != m_debugInfo.cend()) {
436  const ChunkDebugInfo &info = kv->second;
437  line--;
438  execLine--;
439  int uh = (range - 1) / 2;
440  int dh = range - uh - 1;
441  int beg = line - uh;
442  beg = (beg < 0) ? 0 : beg;
443  int end = line + dh;
444  int ssize = static_cast<int>(info.srcLines.size());
445  end = (end >= ssize) ? ssize - 1 : end;
446  for (int i = beg; i <= end; i++) {
447  char execMark = (i == execLine) ? '>' : ' ';
448  char bpMark = (info.validLines[i]) ? 'o' : ' ';
449  bpMark = (info.breakPoints[i]) ? '*' : bpMark;
450  debug::writef("%c%c%6d: %s", execMark, bpMark, i + 1,
451  info.srcLines.at(i).substr(0, 69).c_str());
452  }
453  }
454  else {
455  debug::writef("Debug info not found: %s", name.c_str());
456  }
457 }
458 
459 void LuaDebugger::printLocalAndUpvalue(lua_Debug *ar, int maxDepth, bool skipNoName)
460 {
461  lua_State *L = m_L;
462  {
463  int n = 1;
464  const char *name = nullptr;
465  debug::writeLine(L"Local variables:");
466  while ((name = lua_getlocal(L, ar, n)) != nullptr) {
467  if (!skipNoName || name[0] != '(') {
468  debug::writef_nonl("[%3d] %s = ", n, name);
469  for (const auto &val : luaValueToStrList(L, -1, maxDepth)) {
470  debug::writeLine(val.c_str());
471  }
472  }
473  // pop value
474  lua_pop(L, 1);
475  n++;
476  }
477  }
478  {
479  // push current frame function
480  lua_getinfo(L, "f", ar);
481  int n = 1;
482  const char *name = nullptr;
483  debug::writeLine(L"Upvalues:");
484  while ((name = lua_getupvalue(L, -1, n)) != nullptr) {
485  debug::writef_nonl("[%3d] %s = ", n, name);
486  for (const auto &val : luaValueToStrList(L, -1, maxDepth)) {
487  debug::writeLine(val.c_str());
488  }
489  // pop value
490  lua_pop(L, 1);
491  n++;
492  }
493  // pop function
494  lua_pop(L, 1);
495  }
496 }
497 
498 // _ENV proxy meta-method for eval
499 namespace {
500 int evalIndex(lua_State *L)
501 {
502  // param[1] = table, param[2] = key
503  // upvalue[1] = orig _ENV, upvalue[2] = lua_Debug *
504  ASSERT(lua_istable(L, lua_upvalueindex(1)));
505  ASSERT(lua_islightuserdata(L, lua_upvalueindex(2)));
506  auto *ar = static_cast<lua_Debug *>(
507  lua_touserdata(L, lua_upvalueindex(2)));
508  ASSERT(ar != nullptr);
509 
510  if (lua_isstring(L, 2)) {
511  const char *key = lua_tostring(L, 2);
512  // (1) local variables
513  {
514  int n = 1;
515  const char *name = nullptr;
516  while ((name = lua_getlocal(L, ar, n)) != nullptr) {
517  if (name[0] != '(' && std::strcmp(key, name) == 0) {
518  // return stack top value
519  return 1;
520  }
521  lua_pop(L, 1);
522  n++;
523  }
524  }
525  // (2) upvalues
526  {
527  // push current frame function
528  lua_getinfo(L, "f", ar);
529  int n = 1;
530  const char *name = nullptr;
531  while ((name = lua_getupvalue(L, -1, n)) != nullptr) {
532  if (std::strcmp(key, name) == 0) {
533  // return stack top value
534  return 1;
535  }
536  lua_pop(L, 1);
537  n++;
538  }
539  // pop function
540  lua_pop(L, 1);
541  }
542  }
543  // (3) return _ENV[key]
544  lua_pushvalue(L, 2);
545  lua_gettable(L, lua_upvalueindex(1));
546  return 1;
547 }
548 
549 int evalNewIndex(lua_State *L)
550 {
551  // param[1] = table, param[2] = key, param[3] = value
552  // upvalue[1] = orig _ENV, upvalue[2] = lua_Debug *
553  ASSERT(lua_istable(L, lua_upvalueindex(1)));
554  ASSERT(lua_islightuserdata(L, lua_upvalueindex(2)));
555  auto *ar = static_cast<lua_Debug *>(
556  lua_touserdata(L, lua_upvalueindex(2)));
557  ASSERT(ar != nullptr);
558 
559  if (lua_isstring(L, 2)) {
560  const char *key = lua_tostring(L, 2);
561  // (1) local variables
562  {
563  int n = 1;
564  const char *name = nullptr;
565  while ((name = lua_getlocal(L, ar, n)) != nullptr) {
566  if (name[0] != '(' && std::strcmp(key, name) == 0) {
567  lua_pushvalue(L, 3);
568  lua_setlocal(L, ar, n);
569  return 0;
570  }
571  lua_pop(L, 1);
572  n++;
573  }
574  }
575  // (2) upvalues
576  {
577  // push current frame function
578  lua_getinfo(L, "f", ar);
579  int n = 1;
580  const char *name = nullptr;
581  while ((name = lua_getupvalue(L, -1, n)) != nullptr) {
582  if (std::strcmp(key, name) == 0) {
583  lua_pushvalue(L, 3);
584  lua_setlocal(L, ar, n);
585  return 0;
586  }
587  lua_pop(L, 1);
588  n++;
589  }
590  // pop function
591  lua_pop(L, 1);
592  }
593  }
594  // (3) _ENV[key] = value
595  lua_pushvalue(L, 2);
596  lua_pushvalue(L, 3);
597  lua_settable(L, lua_upvalueindex(1));
598  return 0;
599 }
600 
601 int evalPairs(lua_State *L)
602 {
603  // param[1] = eval_ENV
604  // return next, table, nil
605  // upvalue[1] = orig _ENV, upvalue[2] = lua_Debug *
606  ASSERT(lua_istable(L, lua_upvalueindex(1)));
607  ASSERT(lua_islightuserdata(L, lua_upvalueindex(2)));
608  auto *ar = static_cast<lua_Debug *>(
609  lua_touserdata(L, lua_upvalueindex(2)));
610  ASSERT(ar != nullptr);
611 
612  lua_getglobal(L, "pairs");
613  lua_pushvalue(L, lua_upvalueindex(1));
614  lua_call(L, 1, 3);
615  return 3;
616 }
617 } // namespace
618 
619 void LuaDebugger::pushLocalEnv(lua_Debug *ar, int frameNo)
620 {
621  lua_State *L = m_L;
622 
623  int ret = lua_getstack(L, frameNo, ar);
624  ASSERT(ret);
625  ret = lua_getinfo(L, "nSltu", ar);
626  ASSERT(ret);
627 
628  // push another _ENV for eval
629  lua_newtable(L);
630  int tabind = lua_gettop(L);
631 
632  // push metatable for another _ENV
633  lua_newtable(L);
634 
635  lua_pushliteral(L, "__metatable");
636  lua_pushliteral(L, "read only table");
637  lua_settable(L, -3);
638 
639  lua_pushliteral(L, "__index");
640  // upvalue[1] = orig _ENV
641  lua_getglobal(L, "_G");
642  ASSERT(lua_istable(L, -1));
643  // upvalue[2] = lua_Debug
644  lua_pushlightuserdata(L, ar);
645  lua_pushcclosure(L, evalIndex, 2);
646  lua_settable(L, -3);
647 
648  lua_pushliteral(L, "__newindex");
649  // upvalue[1] = orig _ENV
650  lua_getglobal(L, "_G");
651  // upvalue[2] = lua_Debug
652  lua_pushlightuserdata(L, ar);
653  lua_pushcclosure(L, evalNewIndex, 2);
654  lua_settable(L, -3);
655 
656  lua_pushliteral(L, "__pairs");
657  // upvalue[1] = orig _ENV
658  lua_getglobal(L, "_G");
659  // upvalue[2] = lua_Debug
660  lua_pushlightuserdata(L, ar);
661  lua_pushcclosure(L, evalPairs, 2);
662  lua_settable(L, -3);
663 
664  lua_setmetatable(L, -2);
665 }
666 
667 void LuaDebugger::printEval(const std::string &expr)
668 {
669  lua_State *L = m_L;
670  try {
671  // load str and push function
672  // try "return <expr>;"
673  std::string addret("return ");
674  addret += expr;
675  addret += ';';
676  int ret = luaL_loadstring(L, addret.c_str());
677  if (ret != LUA_OK) {
678  // try "<expr>"
679  ret = luaL_loadstring(L, expr.c_str());
680  if (ret != LUA_OK) {
681  throw LuaError("load failed", L);
682  }
683  }
684 
685  lua_Debug ar = { 0 };
686  // overwrite upvalue[1] (_ENV) of chunk
687  pushLocalEnv(&ar, m_currentFrame);
688  const char * uvName = lua_setupvalue(L, -2, 1);
689  ASSERT(std::strcmp(uvName, "_ENV") == 0);
690 
691  // <func> => <ret...>
692  int retBase = lua_gettop(L);
693  ret = lua_pcall(L, 0, LUA_MULTRET, 0);
694  if (ret != LUA_OK) {
695  throw LuaError("pcall failed", L);
696  }
697 
698  int retLast = lua_gettop(L);
699  if (retBase > retLast) {
700  debug::writeLine(L"OK (No return values)");
701  }
702  else {
703  for (int i = retBase; i <= retLast; i++) {
704  for (const auto &val : luaValueToStrList(L, i, DefTableDepth)) {
705  debug::writeLine(val.c_str());
706  }
707  }
708  }
709  lua_settop(L, retBase);
710  }
711  catch (const LuaError &ex) {
712  debug::writeLine(ex.what());
713  }
714 }
715 
716 void LuaDebugger::printWatchList()
717 {
718  if (m_watchList.size() != 0) {
719  debug::writeLine("Watch List:");
720  }
721  int i = 0;
722  for (const auto &expr : m_watchList) {
723  debug::writef("[%d]%s", i, expr.c_str());
724  printEval(expr);
725  i++;
726  }
727 }
728 
730 // Commands
732 bool LuaDebugger::help(const wchar_t *usage, const std::vector<std::wstring> &args)
733 {
734  if (args.empty()) {
735  for (const auto &entry : CmdList) {
736  debug::writef(L"%s\n\t%s", entry.usage, entry.brief);
737  }
738  }
739  else {
740  for (const auto &cmdstr : args) {
741  const CmdEntry *entry = searchCommandList(cmdstr);
742  if (entry == nullptr) {
743  debug::writef(L"[LuaDbg] Command not found: %s", cmdstr.c_str());
744  return false;
745  }
746  debug::writef(L"%s\nUsage: %s\n\n%s",
747  entry->brief, entry->usage, entry->description);
748  }
749  }
750  return false;
751 }
752 
753 bool LuaDebugger::conf(const wchar_t *usage, const std::vector<std::wstring> &args)
754 {
755  debug::writeLine(LUA_COPYRIGHT);
756  debug::writeLine(LUA_AUTHORS);
758  debug::writeLine(L"lua_Integer");
759  debug::writef(L" 0x%llx-0x%llx",
760  static_cast<long long>(LUA_MININTEGER),
761  static_cast<long long>(LUA_MAXINTEGER));
762  debug::writef(L" (%lld)-(%lld)",
763  static_cast<long long>(LUA_MININTEGER),
764  static_cast<long long>(LUA_MAXINTEGER));
765  debug::writeLine(L"lua_Number");
766  debug::writef(L" abs range : (%.16Le) - (%.16Le)",
767  static_cast<long double>(std::numeric_limits<lua_Number>::min()),
768  static_cast<long double>(std::numeric_limits<lua_Number>::max()));
769  debug::writef(L" 10-base digits : %d",
770  std::numeric_limits<lua_Number>::digits10);
771  debug::writef(L" 10-base exp range : (%d) - (%d)",
772  std::numeric_limits<lua_Number>::min_exponent10,
773  std::numeric_limits<lua_Number>::max_exponent10);
774  return false;
775 }
776 
777 bool LuaDebugger::mem(const wchar_t *usage, const std::vector<std::wstring> &args)
778 {
779  lua_State *L = m_L;
780 
781  bool gc = false;
782  for (const auto &str : args) {
783  if (str == L"-gc") {
784  gc = true;
785  }
786  else {
787  debug::writeLine(usage);
788  return false;
789  }
790  }
791 
792  if (gc) {
793  lua_gc(L, LUA_GCCOLLECT, 0);
794  }
795 
796  int kb = lua_gc(L, LUA_GCCOUNT, 0);
797  int b = kb * 1024 + lua_gc(L, LUA_GCCOUNTB, 0);
798  debug::writef(L"%.3f / %.1f KiB", b / 1024.0, m_heapSize / 1024.0);
799 
800  return false;
801 }
802 
803 bool LuaDebugger::bt(const wchar_t *usage, const std::vector<std::wstring> &args)
804 {
805  lua_State *L = m_L;
806  int lv = 0;
807  lua_Debug ar = { 0 };
808  while (lua_getstack(L, lv, &ar)) {
809  lua_getinfo(L, "nSltu", &ar);
810  const char *name = (ar.name == nullptr) ? "?" : ar.name;
811  debug::writef("[frame #%d] %s (%s) %s:%d", lv,
812  name, ar.what, ar.source, ar.currentline);
813  lv++;
814  }
815  return false;
816 }
817 
818 bool LuaDebugger::fr(const wchar_t *usage, const std::vector<std::wstring> &args)
819 {
820  int lv = m_currentFrame;
821  try {
822  if (args.size() == 1) {
823  lv = stoi_s(args[0], 10);
824  }
825  else if (args.size() >= 2) {
826  throw std::invalid_argument("invalid argument");
827  }
828  }
829  catch (...) {
830  debug::writeLine(usage);
831  return false;
832  }
833 
834  lua_State *L = m_L;
835  lua_Debug ar = { 0 };
836  if (lua_getstack(L, lv, &ar)) {
837  lua_getinfo(L, "nSltu", &ar);
838  const char *name = (ar.name == nullptr) ? "?" : ar.name;
839  debug::writef("[frame #%d] %s (%s) %s:%d", lv,
840  name, ar.what, ar.source, ar.currentline);
841  printSrcLines(ar.source, ar.currentline, DefSrcLines, ar.currentline);
842  printLocalAndUpvalue(&ar, 0, true);
843  // set current frame
844  m_currentFrame = lv;
845  }
846  else {
847  debug::writef(L"Invalid frame No: %d", lv);
848  }
849  return false;
850 }
851 
852 bool LuaDebugger::src(const wchar_t *usage, const std::vector<std::wstring> &args)
853 {
854  lua_State *L = m_L;
855 
856  lua_Debug ar = { 0 };
857  if (!lua_getstack(L, m_currentFrame, &ar)) {
858  debug::writeLine(L"Cannot get current frame info");
859  return false;
860  }
861  lua_getinfo(L, "Sl", &ar);
862 
863  // [-f <file>] [<line>] [-n <numlines>] [-all]
864  std::string fileName = ar.source;
865  int line = ar.currentline;
866  int num = DefSrcLines;
867  bool all = false;
868  try {
869  for (size_t i = 0; i < args.size(); i++) {
870  if (args[i] == L"-f") {
871  i++;
872  fileName = util::wc2utf8(args.at(i).c_str()).get();
873  }
874  else if (args[i] == L"-n") {
875  i++;
876  num = stoi_s(args.at(i));
877  }
878  else if (args[i] == L"-all") {
879  all = true;
880  }
881  else {
882  line = stoi_s(args[i]);
883  }
884  }
885  }
886  catch (...) {
887  debug::writeLine(usage);
888  return false;
889  }
890 
891  num = all ? 0x00ffffff : num;
892  printSrcLines(fileName, line, num);
893  return false;
894 }
895 
896 bool LuaDebugger::eval(const wchar_t *usage, const std::vector<std::wstring> &args)
897 {
898  if (args.empty()) {
899  debug::writeLine(usage);
900  return false;
901  }
902 
903  lua_State *L = m_L;
904 
905  // concat args[1]..[n-1]
906  std::wstring wsrc;
907  for (const auto &str : args) {
908  wsrc += str;
909  wsrc += L' ';
910  }
911  auto src = util::wc2utf8(wsrc.c_str());
912 
913  printEval(src.get());
914 
915  return false;
916 }
917 
918 bool LuaDebugger::watch(const wchar_t *usage, const std::vector<std::wstring> &args)
919 {
920  // watch [<lua_script> | -d <number>]
921  std::vector<int> delInd;
922  std::string addScript;
923  try {
924  if (args.empty()) {
925  // do nothing
926  }
927  else if (args[0] == L"-d") {
928  for (size_t i = 0; i < args.size(); i++) {
929  delInd.push_back(stoi_s(args[i]));
930  }
931  }
932  else {
933  for (size_t i = 0; i < args.size(); i++) {
934  addScript += ' ';
935  addScript += util::wc2utf8(args[i].c_str()).get();
936  }
937  }
938  }
939  catch (...) {
940  debug::writeLine(usage);
941  return false;
942  }
943  // delete
944  std::sort(delInd.begin(), delInd.end(), std::greater<int>());
945  for (int ind : delInd) {
946  if (ind < 0 || static_cast<size_t>(ind) >= m_watchList.size()) {
947  debug::writef(L"Cannot delete: %d", ind);
948  continue;
949  }
950  m_watchList.erase(m_watchList.begin() + ind);
951  }
952  // add
953  if (!addScript.empty()) {
954  m_watchList.emplace_back(addScript);
955  }
956  // print
957  printWatchList();
958  return false;
959 }
960 
961 bool LuaDebugger::c(const wchar_t *usage, const std::vector<std::wstring> &args)
962 {
963  debug::writeLine(L"[LuaDbg] continue...");
964  m_debugState = DebugState::CONT;
965  return true;
966 }
967 
968 bool LuaDebugger::s(const wchar_t *usage, const std::vector<std::wstring> &args)
969 {
970  debug::writeLine(L"[LuaDbg] step in...");
971  m_debugState = DebugState::BREAK_LINE_ANY;
972  return true;
973 }
974 
975 bool LuaDebugger::n(const wchar_t *usage, const std::vector<std::wstring> &args)
976 {
977  debug::writeLine(L"[LuaDbg] step over...");
978  m_callDepth = 0;
979  m_debugState = DebugState::BREAK_LINE_DEPTH0;
980  return true;
981 }
982 
983 bool LuaDebugger::out(const wchar_t *usage, const std::vector<std::wstring> &args)
984 {
985  debug::writeLine(L"[LuaDbg] step out...");
986  m_callDepth = 1;
987  m_debugState = DebugState::BREAK_LINE_DEPTH0;
988  return true;
989 }
990 
991 bool LuaDebugger::bp(const wchar_t *usage, const std::vector<std::wstring> &args)
992 {
993  lua_State *L = m_L;
994  std::string fileName;
995  std::vector<int> lines;
996 
997  // default is current frame source file
998  lua_Debug ar = { 0 };
999  if (lua_getstack(L, m_currentFrame, &ar)) {
1000  lua_getinfo(L, "S", &ar);
1001  fileName = ar.source;
1002  }
1003  // bp [-d] [-f <filename>] [<line>...]
1004  bool del = false;
1005  try {
1006  for (size_t i = 0; i < args.size(); i++) {
1007  if (args[i] == L"-d") {
1008  del = true;
1009  }
1010  else if (args[i] == L"-f") {
1011  i++;
1012  fileName = util::wc2utf8(args.at(i).c_str()).get();
1013  }
1014  else {
1015  lines.push_back(stoi_s(args[i], 10));
1016  }
1017  }
1018  }
1019  catch (...) {
1020  debug::writeLine(usage);
1021  return false;
1022  }
1023  // set/delete
1024  for (int line : lines) {
1025  auto kv = m_debugInfo.find(fileName);
1026  if (kv == m_debugInfo.cend()) {
1027  debug::writef("Error: Debug info not found: \"%s\"", fileName.c_str());
1028  continue;
1029  }
1030  ChunkDebugInfo &info = kv->second;
1031  if (line < 1 || static_cast<size_t>(line) > info.breakPoints.size()) {
1032  debug::writef("Error: %s:%d is out of range", fileName.c_str(), line);
1033  continue;
1034  }
1035  info.breakPoints[line - 1] = del ? 0 : 1;
1036  }
1037  // print
1038  debug::writeLine(L"Breakpoints:");
1039  for (const auto &kv : m_debugInfo) {
1040  const ChunkDebugInfo &info = kv.second;
1041  debug::writef("[%s]", info.chunkName.c_str());
1042  bool any = false;
1043  for (size_t i = 0; i < info.breakPoints.size(); i++) {
1044  if (info.breakPoints[i]) {
1045  debug::writef(L"%d", i + 1);
1046  any = true;
1047  }
1048  }
1049  if (!any) {
1050  debug::writeLine(L"(No breakpoints)");
1051  }
1052  }
1053 
1054  return false;
1055 }
1056 
1057 } // namespace debugger
1058 } // namespace lua
1059 } // namespace yappy
bool c(const wchar_t *usage, const std::vector< std::wstring > &args)
std::unique_ptr< char[]> wc2utf8(const wchar_t *in)
Wide char to UTF-8.
Definition: util.h:105
void write(const wchar_t *str, bool newline) noexcept
Write debug string.
Definition: debug.cpp:73
lua_State * getLuaState() const
Get Lua state.
LuaDebugger * dbg
const char * str
Definition: input.cpp:197
const wchar_t * usage
Debug utilities.
const wchar_t * cmd
bool fr(const wchar_t *usage, const std::vector< std::wstring > &args)
const wchar_t * description
void writef(const wchar_t *fmt,...) noexcept
Write debug message using format string like printf.
Definition: debug.cpp:103
bool out(const wchar_t *usage, const std::vector< std::wstring > &args)
bool help(const wchar_t *usage, const std::vector< std::wstring > &args)
bool src(const wchar_t *usage, const std::vector< std::wstring > &args)
#define ASSERT(x)
Assertion which uses debug framework.
Definition: debug.h:18
Definition: config.cpp:6
void writeLine(const wchar_t *str=L"") noexcept
Write debug string and new line.
Definition: debug.h:64
void loadDebugInfo(const char *name, const char *src, size_t size)
Load debug info from chunk name and source string.
bool watch(const wchar_t *usage, const std::vector< std::wstring > &args)
bool conf(const wchar_t *usage, const std::vector< std::wstring > &args)
char msg[LINE_DATA_SIZE-sizeof(LARGE_INTEGER)-sizeof(uint32_t)]
Definition: debug.cpp:159
bool bp(const wchar_t *usage, const std::vector< std::wstring > &args)
bool s(const wchar_t *usage, const std::vector< std::wstring > &args)
LuaDebugger(lua_State *L, bool debugEnable, int instLimit, size_t heapSize)
Constructor.
bool mem(const wchar_t *usage, const std::vector< std::wstring > &args)
void checkWin32Result(bool cond, const std::string &msg)
Definition: exceptions.h:50
const wchar_t * brief
void writef_nonl(const wchar_t *fmt,...) noexcept
Write debug message using format string like printf. (No new line)
Definition: debug.cpp:125
bool(LuaDebugger::* exec)(const wchar_t *usage, const std::vector< std::wstring > &argv)
bool eval(const wchar_t *usage, const std::vector< std::wstring > &args)
void pcall(int narg, int nret, bool autoBreak)
Prepare and call lua_pcall().
bool bt(const wchar_t *usage, const std::vector< std::wstring > &args)
bool n(const wchar_t *usage, const std::vector< std::wstring > &args)
std::vector< std::string > luaValueToStrList(lua_State *L, int ind, int maxDepth)
Definition: script.cpp:346