├── Makefile ├── LICENSE ├── dump.lua ├── README.md ├── lsnapshot.lua └── snapshot.c /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : all linux mingw 2 | 3 | all : macosx 4 | 5 | linux : 6 | gcc -g -DDUMP_STRING -Wall -fPIC --shared -o snapshot.so snapshot.c 7 | 8 | mingw : 9 | gcc -g -Wall --shared -o snapshot.dll snapshot.c -I/usr/local/include -L/usr/local/bin -llua53 10 | 11 | mingw51 : 12 | gcc -g -Wall --shared -o snapshot.dll snapshot.c -I/usr/local/include -L/usr/local/bin -llua51 13 | 14 | macosx : 15 | gcc -g -DDUMP_STRING -Wall --shared -undefined dynamic_lookup -I/Users/zixun/codes/lua/lua-5.4.2/src -o snapshot.so snapshot.c -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 codingow.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /dump.lua: -------------------------------------------------------------------------------- 1 | local lss = require "lsnapshot" 2 | 3 | local sss = nil 4 | lss.start_snapshot() 5 | 6 | local tmp = {} 7 | sss = {} 8 | local function foo() 9 | return sss 10 | end 11 | local k = {} 12 | k[1] = 1 13 | k[2] = 2 14 | local function test() 15 | sss[1] = 1 16 | return foo() 17 | end 18 | 19 | local function ff() 20 | k[3] = 3 21 | end 22 | 23 | local function dd() 24 | ff() 25 | end 26 | 27 | local function ee() 28 | return k[1] 29 | end 30 | 31 | local function bb() 32 | dd() 33 | end 34 | 35 | local function cc() 36 | ee() 37 | end 38 | 39 | local function aa(p) 40 | bb() 41 | cc() 42 | return p 43 | end 44 | 45 | local p = test() 46 | 47 | aa(p) 48 | 49 | lss.dstop_snapshot(40) 50 | 51 | --[[ 52 | $ lua dump.lua 53 | ------------------ diff snapshot ------------------ 54 | [1] type:(T) addr:0x5648cc8274f0 size:0.1171875KB 55 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(T)k{#dump.lua:0} 56 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:19)ff{#dump.lua:0}->(T)k 57 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:27)ee{#dump.lua:0}->(T)k 58 | [2] type:(T) addr:0x5648cc827450 size:0.0703125KB 59 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(T)p{#dump.lua:0} 60 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:14)test{#dump.lua:0}->(T)sss 61 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:8)foo{#dump.lua:0}->(T)sss 62 | [3] type:(T) addr:0x5648cc8273c0 size:0.0546875KB 63 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(T)tmp{#dump.lua:0} 64 | [4] type:(L@dump.lua:39) addr:0x5648cc827600 size:0.046875KB 65 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:39)aa{#dump.lua:0} 66 | [5] type:(L@dump.lua:14) addr:0x5648cc827530 size:0.046875KB 67 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:14)test{#dump.lua:0} 68 | [6] type:(L@dump.lua:19) addr:0x5648cc814f30 size:0.0390625KB 69 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:19)ff{#dump.lua:0} 70 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:23)dd{#dump.lua:0}->(L@dump.lua:19)ff 71 | [7] type:(L@dump.lua:31) addr:0x5648cc827760 size:0.0390625KB 72 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:31)bb{#dump.lua:0} 73 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:39)aa{#dump.lua:0}->(L@dump.lua:31)bb 74 | [8] type:(L@dump.lua:35) addr:0x5648cc8150e0 size:0.0390625KB 75 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:35)cc{#dump.lua:0} 76 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:39)aa{#dump.lua:0}->(L@dump.lua:35)cc 77 | [9] type:(L@dump.lua:8) addr:0x5648cc827280 size:0.0390625KB 78 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:8)foo{#dump.lua:0} 79 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:14)test{#dump.lua:0}->(L@dump.lua:8)foo 80 | [10] type:(L@dump.lua:27) addr:0x5648cc8276b0 size:0.0390625KB 81 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:27)ee{#dump.lua:0} 82 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:35)cc{#dump.lua:0}->(L@dump.lua:27)ee 83 | [11] type:(L@dump.lua:23) addr:0x5648cc827590 size:0.0390625KB 84 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:23)dd{#dump.lua:0} 85 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:31)bb{#dump.lua:0}->(L@dump.lua:23)dd 86 | --------------- all size:0.1171875Kb --------------- 87 | ]] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-snapshot 2 | ============ 3 | 4 | Make a snapshot for lua state to detect memory leaks. 5 | 6 | See dump.lua for example. 7 | 8 | Build 9 | ===== 10 | 11 | make linux 12 | 13 | or 14 | 15 | make mingw (in windows) 16 | 17 | or 18 | 19 | make macosx 20 | 21 | 22 | tutorial 23 | === 24 | 25 | 如下示例: 26 | 27 | ~~~.lua 28 | local lss = require "lsnapshot" 29 | 30 | local sss = nil 31 | lss.start_snapshot() 32 | 33 | local tmp = {} 34 | sss = {} 35 | local function foo() 36 | return sss 37 | end 38 | local k = {} 39 | k[1] = 1 40 | k[2] = 2 41 | local function test() 42 | sss[1] = 1 43 | return foo() 44 | end 45 | 46 | local function ff() 47 | k[3] = 3 48 | end 49 | 50 | local function dd() 51 | ff() 52 | end 53 | 54 | local function ee() 55 | return k[1] 56 | end 57 | 58 | local function bb() 59 | dd() 60 | end 61 | 62 | local function cc() 63 | ee() 64 | end 65 | 66 | local function aa(p) 67 | bb() 68 | cc() 69 | return p 70 | end 71 | 72 | local p = test() 73 | 74 | aa(p) 75 | 76 | lss.dstop_snapshot(40) 77 | ~~~ 78 | 79 | 在需要做快照的地方调用`start_snapshot`, 结尾处调用`dstop_snapshot([count])`, 其中count为要打印的对象个数,不填的话会全部打印。 80 | `dstop_snapshot` 会列出从`start_snapshot`开始到结束创建出来的仍然被持有的对象,结果会根据size按照从大到小排序. 81 | 82 | 执行输出结果: 83 | ``` 84 | $ lua dump.lua 85 | ------------------ diff snapshot ------------------ 86 | [1] type:(T) addr:0x5648cc8274f0 size:0.1171875KB 87 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(T)k{#dump.lua:0} 88 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:19)ff{#dump.lua:0}->(T)k 89 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:27)ee{#dump.lua:0}->(T)k 90 | [2] type:(T) addr:0x5648cc827450 size:0.0703125KB 91 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(T)p{#dump.lua:0} 92 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:14)test{#dump.lua:0}->(T)sss 93 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:8)foo{#dump.lua:0}->(T)sss 94 | [3] type:(T) addr:0x5648cc8273c0 size:0.0546875KB 95 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(T)tmp{#dump.lua:0} 96 | [4] type:(L@dump.lua:39) addr:0x5648cc827600 size:0.046875KB 97 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:39)aa{#dump.lua:0} 98 | [5] type:(L@dump.lua:14) addr:0x5648cc827530 size:0.046875KB 99 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:14)test{#dump.lua:0} 100 | [6] type:(L@dump.lua:19) addr:0x5648cc814f30 size:0.0390625KB 101 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:19)ff{#dump.lua:0} 102 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:23)dd{#dump.lua:0}->(L@dump.lua:19)ff 103 | [7] type:(L@dump.lua:31) addr:0x5648cc827760 size:0.0390625KB 104 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:31)bb{#dump.lua:0} 105 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:39)aa{#dump.lua:0}->(L@dump.lua:31)bb 106 | [8] type:(L@dump.lua:35) addr:0x5648cc8150e0 size:0.0390625KB 107 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:35)cc{#dump.lua:0} 108 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:39)aa{#dump.lua:0}->(L@dump.lua:35)cc 109 | [9] type:(L@dump.lua:8) addr:0x5648cc827280 size:0.0390625KB 110 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:8)foo{#dump.lua:0} 111 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:14)test{#dump.lua:0}->(L@dump.lua:8)foo 112 | [10] type:(L@dump.lua:27) addr:0x5648cc8276b0 size:0.0390625KB 113 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:27)ee{#dump.lua:0} 114 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:35)cc{#dump.lua:0}->(L@dump.lua:27)ee 115 | [11] type:(L@dump.lua:23) addr:0x5648cc827590 size:0.0390625KB 116 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:23)dd{#dump.lua:0} 117 | Root->(T)[registry]->(L@thread ./lsnapshot.lua:266 dump.lua:49 [C])[1]->(L@dump.lua:31)bb{#dump.lua:0}->(L@dump.lua:23)dd 118 | --------------- all size:0.5703125Kb --------------- 119 | ``` 120 | 其中`[X]`为索引, 第二个字段包含`(T/U/L/C/S)`. 121 | * `(T)`表示的为table 类型 122 | * `(U)`表示userdata 类型 123 | * `(L)`表示lua function类型,包含函数路径 124 | * `(C)`表示cfunction 类型 125 | * `(S)`表示thread 类型 126 | * `(A)`表示string 类型 127 | 128 | 第三个为fullpath路径. `size:0.0390625KB` 表示的为改对象占用的大小 129 | 130 | -------------------------------------------------------------------------------- /lsnapshot.lua: -------------------------------------------------------------------------------- 1 | local ss = require "snapshot" 2 | local snapshot = ss.snapshot 3 | local str2ud = ss.str2ud 4 | local ud2str = ss.ud2str 5 | local sformat = string.format 6 | local tconcat = table.concat 7 | local M = {} 8 | local Root = str2ud("0") 9 | 10 | local t2simple = { 11 | table = "(T)", 12 | userdata = "(U)", 13 | -- ["function"] = "(L)", 14 | thread = "(S)", 15 | cfunction = "(C)", 16 | string = "(A)", 17 | } 18 | 19 | local begin_s = nil 20 | function M.start_snapshot() 21 | begin_s = snapshot() 22 | end 23 | 24 | local function parser_record(s) 25 | local t, sz = string.match(s, "^([^{}]+) {(%d+)}") 26 | local parents = {} 27 | for parent, field in string.gmatch(s, "\n([^%s]+) : ([^\n\0]+)") do 28 | parents[#parents+1] = { 29 | parent = str2ud(parent), 30 | field = field, 31 | } 32 | end 33 | return { 34 | type = assert(t), 35 | size = tonumber(assert(sz)), 36 | parents = parents, 37 | } 38 | end 39 | 40 | local function reshape_snapshot(s, full_snapshot) 41 | local reshape = {} 42 | local function add_reshape(k, v, is_new) 43 | local record = parser_record(v) 44 | local t = record.type 45 | local parents = record.parents 46 | local st = t2simple[t] or sformat("(L@%s)", t) 47 | reshape[k] = { 48 | t = t, 49 | size = record.size, 50 | st = st, 51 | parents = parents, 52 | fullpath = nil, 53 | addr = k, 54 | is_new = is_new, 55 | } 56 | 57 | for _, parent_item in ipairs(parents) do 58 | local pk = parent_item.parent 59 | local pv = full_snapshot[pk] 60 | if not reshape[pk] and pv then 61 | add_reshape(pk, pv, false) 62 | end 63 | end 64 | end 65 | 66 | for k, v in pairs(s) do 67 | add_reshape(k, v, true) 68 | end 69 | 70 | local function concat_path(list, count) 71 | local len = #list 72 | count = count or len 73 | local t = {} 74 | for i=len, len-count+1, -1 do 75 | t[#t+1] = list[i] 76 | end 77 | return tconcat(t, "->") 78 | end 79 | 80 | local deep = 0 81 | local function gen_fullname(addr, list, map) 82 | list = list or {} 83 | map = map or {} 84 | local entry = reshape[addr] 85 | local fullpath = entry.fullpath 86 | if map[addr] then 87 | return false 88 | end 89 | 90 | map[addr] = true 91 | if fullpath then 92 | list[#list+1] = fullpath 93 | return true 94 | end 95 | 96 | local parents = entry.parents 97 | -- not parent 98 | if not next(parents) then 99 | list[#list+1] = "NORoot" 100 | entry.fullpath = "NORoot" 101 | return true 102 | end 103 | 104 | for _, parent_item in ipairs(parents) do 105 | local pv = parent_item.parent 106 | local pk = parent_item.field 107 | -- root parent 108 | if pv == Root then 109 | list[#list+1] = entry.st .. pk 110 | list[#list+1] = "Root" 111 | entry.fullpath = concat_path(list, 2) 112 | return true 113 | end 114 | 115 | -- not find parent 116 | local parent_entry = reshape[pv] 117 | if not parent_entry then 118 | list[#list+1] = pk 119 | list[#list+1] = sformat("{%s}", ud2str(pv)) 120 | entry.fullpath = concat_path(list, 2) 121 | return true 122 | end 123 | 124 | -- is too deep 125 | if deep >= 64 then 126 | list[#list+1] = pk 127 | list[#list+1] = "!PathTooDeep...!" 128 | entry.fullpath = concat_path(list, 2) 129 | return true 130 | end 131 | 132 | local st = entry.st 133 | local idx = #list+1 134 | list[idx] = st .. pk 135 | deep = deep + 1 136 | local b = gen_fullname(pv, list, map) 137 | deep = deep - 1 138 | if b then 139 | entry.fullpath = concat_path(list, #list-idx+1) 140 | return true 141 | else 142 | assert(#list == idx) 143 | list[idx] = nil 144 | map[pv] = nil 145 | end 146 | end 147 | if #parents>0 then 148 | local pv1 = sformat("{%s}", ud2str(parents[1].parent)) 149 | local pk1 = parents[1].field 150 | entry.fullpath = concat_path({pk1, pv1}) 151 | else 152 | entry.fullpath = sformat("{%s}", ud2str(addr)) 153 | end 154 | end 155 | 156 | for addr, entry in pairs(reshape) do 157 | gen_fullname(addr) 158 | assert(entry.fullpath) 159 | end 160 | 161 | local ret = {} 162 | for k,v in pairs(reshape) do 163 | if v.is_new then 164 | ret[#ret+1] = v 165 | local parents = v.parents 166 | for _, parent_item in ipairs(parents) do 167 | local parent_entry = reshape[parent_item.parent] 168 | local fullpath = parent_item.parent == Root and "Root" or parent_entry.fullpath 169 | parent_item.parent_fullpath = assert(fullpath) 170 | parent_item.parent_st = parent_entry and parent_entry.st or "" 171 | end 172 | end 173 | end 174 | return ret 175 | end 176 | 177 | local function diff_snapshot(begin_s, end_s) 178 | local reshape 179 | if not end_s then 180 | reshape = reshape_snapshot(begin_s, begin_s) 181 | else 182 | local diff_s = {} 183 | for k,v in pairs(end_s) do 184 | if begin_s[k] == nil then 185 | diff_s[k] = v 186 | end 187 | end 188 | reshape = reshape_snapshot(diff_s, end_s) 189 | end 190 | table.sort(reshape, function (a, b) 191 | return a.size > b.size 192 | end) 193 | return reshape 194 | end 195 | 196 | local function dump_reshape(reshape, len) 197 | local rlen = #reshape 198 | len = len or rlen 199 | if len < 0 or len > rlen then 200 | len = rlen 201 | end 202 | 203 | local function size_tostring(sz) 204 | if sz < 1024 * 1024 then 205 | return sformat("%sKB", sz / 1024) 206 | elseif sz < 1024 * 1204 * 1024 then 207 | return sformat("%sMB", sz / 1024 / 1024) 208 | else 209 | return sformat("%sGB", sz / 1024 / 1024 / 1024) 210 | end 211 | end 212 | 213 | local function path_tostring(st, parent) 214 | local fullpath = parent.parent_fullpath 215 | local field = parent.field 216 | return fullpath .. "->" .. st .. field 217 | end 218 | 219 | local function entry_tostring(idx, entry) 220 | local t = {} 221 | t[1] = sformat("[%d] type:%s addr:%s size:%s", 222 | idx, entry.st, ud2str(entry.addr), size_tostring(entry.size)) 223 | 224 | local len = #entry.parents 225 | for i=1,len do 226 | if i >= 8 then 227 | t[i+1] = sformat("\tparents more than %d ...", len-i) 228 | break 229 | end 230 | t[i+1] = sformat("\t%s", path_tostring(entry.st, entry.parents[i])) 231 | end 232 | return tconcat(t, "\n") 233 | end 234 | 235 | local all_size = 0 236 | print("------------------ diff snapshot ------------------") 237 | for i=1, rlen do 238 | local v = reshape[i] 239 | all_size = all_size + v.size 240 | if i <= len then 241 | print(entry_tostring(i, v)) 242 | elseif i == len+1 then 243 | print(sformat("more than %d ...", rlen - len)) 244 | end 245 | end 246 | print(sformat("--------------- all size:%sKb ---------------", all_size / 1024)) 247 | end 248 | 249 | function M.dump_snapshot(len, max_objcount) 250 | local end_s = snapshot(max_objcount) 251 | local reshape = diff_snapshot(end_s) 252 | dump_reshape(reshape, len) 253 | end 254 | 255 | function M.dstop_snapshot(len) 256 | if not begin_s then 257 | error("snapshot not begin") 258 | end 259 | for k, _ in pairs(begin_s) do 260 | begin_s[k] = true -- 释放value,end_s中将不会有这些key 261 | end 262 | begin_s[ss.obj2addr(begin_s)] = true -- 消除begin_s的影响 263 | local end_s = snapshot() 264 | local reshape = diff_snapshot(begin_s, end_s) 265 | dump_reshape(reshape, len) 266 | begin_s = nil 267 | end 268 | 269 | return M 270 | -------------------------------------------------------------------------------- /snapshot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "lstate.h" 4 | #include "lstring.h" 5 | #include "ltable.h" 6 | #include "lfunc.h" 7 | 8 | #include "lgc.h" 9 | 10 | #ifdef isshared 11 | static int 12 | check_shared(lua_State* L, int idx, int t) { 13 | switch(t) { 14 | case LUA_TTABLE: 15 | case LUA_TSTRING: { 16 | GCObject* o = (GCObject*)lua_topointer(L, idx); 17 | if(o) { 18 | return isshared(o); 19 | } 20 | } 21 | default: 22 | break; 23 | } 24 | return 0; 25 | } 26 | #endif 27 | 28 | struct snapshot_params { 29 | int max_count; 30 | int current_mark_count; 31 | }; 32 | 33 | static void mark_object(lua_State *L, lua_State *dL, const void * parent, const char * desc, struct snapshot_params* args); 34 | 35 | #define check_limit(L, args) do { \ 36 | if((args)->max_count > 0) { \ 37 | if(((args)->current_mark_count)++ > (args)->max_count) { \ 38 | lua_pop((L),1); \ 39 | return; \ 40 | } \ 41 | } \ 42 | } while(0); 43 | 44 | #if LUA_VERSION_NUM == 501 45 | 46 | static void 47 | luaL_checkversion(lua_State *L) { 48 | if (lua_pushthread(L) == 0) { 49 | luaL_error(L, "Must require in main thread"); 50 | } 51 | lua_setfield(L, LUA_REGISTRYINDEX, "mainthread"); 52 | } 53 | 54 | static void 55 | lua_rawsetp(lua_State *L, int idx, const void *p) { 56 | if (idx < 0) { 57 | idx += lua_gettop(L) + 1; 58 | } 59 | lua_pushlightuserdata(L, (void *)p); 60 | lua_insert(L, -2); 61 | lua_rawset(L, idx); 62 | } 63 | 64 | static void 65 | lua_rawgetp(lua_State *L, int idx, const void *p) { 66 | if (idx < 0) { 67 | idx += lua_gettop(L) + 1; 68 | } 69 | lua_pushlightuserdata(L, (void *)p); 70 | lua_rawget(L, idx); 71 | } 72 | 73 | static void 74 | lua_getuservalue(lua_State *L, int idx) { 75 | lua_getfenv(L, idx); 76 | } 77 | 78 | static void 79 | mark_function_env(lua_State *L, lua_State *dL, const void * t, struct snapshot_params* args) { 80 | lua_getfenv(L,-1); 81 | if (lua_istable(L,-1)) { 82 | mark_object(L, dL, t, "[environment]"); 83 | } else { 84 | lua_pop(L,1); 85 | } 86 | } 87 | 88 | // lua 5.1 has no light c function 89 | #define is_lightcfunction(L, idx) (0) 90 | 91 | #else 92 | 93 | #define mark_function_env(L,dL,t,args) 94 | 95 | static int 96 | is_lightcfunction(lua_State *L, int idx) { 97 | if (lua_iscfunction(L, idx)) { 98 | if (lua_getupvalue(L, idx, 1) == NULL) { 99 | return 1; 100 | } 101 | lua_pop(L, 1); 102 | } 103 | return 0; 104 | } 105 | 106 | #endif 107 | 108 | #if LUA_VERSION_NUM == 504 109 | /* 110 | ** True if value of 'alimit' is equal to the real size of the array 111 | ** part of table 't'. (Otherwise, the array part must be larger than 112 | ** 'alimit'.) 113 | */ 114 | #define limitequalsasize(t) (isrealasize(t) || ispow2((t)->alimit)) 115 | 116 | 117 | /* 118 | ** Returns the real size of the 'array' array 119 | */ 120 | static unsigned int _luaH_realasize (const Table *t) { 121 | if (limitequalsasize(t)) 122 | return t->alimit; /* this is the size */ 123 | else { 124 | unsigned int size = t->alimit; 125 | /* compute the smallest power of 2 not smaller than 'n' */ 126 | size |= (size >> 1); 127 | size |= (size >> 2); 128 | size |= (size >> 4); 129 | size |= (size >> 8); 130 | size |= (size >> 16); 131 | #if (UINT_MAX >> 30) > 3 132 | size |= (size >> 32); /* unsigned int has more than 32 bits */ 133 | #endif 134 | size++; 135 | lua_assert(ispow2(size) && size/2 < t->alimit && t->alimit < size); 136 | return size; 137 | } 138 | } 139 | #endif 140 | 141 | #include 142 | #include 143 | #include 144 | 145 | #define TABLE 1 146 | #define FUNCTION 2 147 | #define SOURCE 3 148 | #define THREAD 4 149 | #define USERDATA 5 150 | #define STRING 6 151 | #define MARK 7 152 | 153 | static bool 154 | ismarked(lua_State *dL, const void *p) { 155 | lua_rawgetp(dL, MARK, p); 156 | if (lua_isnil(dL,-1)) { 157 | lua_pop(dL,1); 158 | lua_pushboolean(dL,1); 159 | lua_rawsetp(dL, MARK, p); 160 | return false; 161 | } 162 | lua_pop(dL,1); 163 | return true; 164 | } 165 | 166 | static const void * 167 | readobject(lua_State *L, lua_State *dL, const void *parent, const char *desc) { 168 | int t = lua_type(L, -1); 169 | #ifdef isshared 170 | if(check_shared(L, -1, t)) { 171 | lua_pop(L, 1); 172 | return NULL; 173 | } 174 | #endif 175 | int tidx = 0; 176 | switch (t) { 177 | case LUA_TTABLE: 178 | tidx = TABLE; 179 | break; 180 | case LUA_TFUNCTION: 181 | if (is_lightcfunction(L, -1)) { 182 | lua_pop(L, 1); 183 | return NULL; 184 | } 185 | tidx = FUNCTION; 186 | break; 187 | case LUA_TTHREAD: 188 | tidx = THREAD; 189 | break; 190 | case LUA_TUSERDATA: 191 | tidx = USERDATA; 192 | break; 193 | #ifdef DUMP_STRING 194 | case LUA_TSTRING: 195 | tidx = STRING; 196 | break; 197 | #endif 198 | default: 199 | lua_pop(L, 1); 200 | return NULL; 201 | } 202 | 203 | const void * p = NULL; 204 | if(t == LUA_TUSERDATA) { 205 | const TValue *o = NULL; 206 | #if LUA_VERSION_NUM == 504 207 | #if LUA_VERSION_RELEASE_NUM < 50405 208 | o = s2v(L->top - 1); 209 | #else 210 | o = s2v(L->top.p - 1); 211 | #endif 212 | #else 213 | o = L->top - 1; 214 | #endif 215 | // const TValue *o = index2value(L, -1); 216 | p = (const void*)uvalue(o); 217 | } else if (t == LUA_TSTRING) { 218 | p = lua_tostring(L, -1); 219 | } 220 | else { 221 | p = (const void*)lua_topointer(L, -1); 222 | } 223 | if (ismarked(dL, p)) { 224 | lua_rawgetp(dL, tidx, p); 225 | if (!lua_isnil(dL,-1)) { 226 | lua_pushstring(dL,desc); 227 | lua_rawsetp(dL, -2, parent); 228 | } 229 | lua_pop(dL,1); 230 | lua_pop(L,1); 231 | return NULL; 232 | } 233 | 234 | lua_newtable(dL); 235 | lua_pushstring(dL,desc); 236 | lua_rawsetp(dL, -2, parent); 237 | lua_rawsetp(dL, tidx, p); 238 | 239 | return p; 240 | } 241 | 242 | static const char * 243 | keystring(lua_State *L, int index, char * buffer) { 244 | int t = lua_type(L,index); 245 | switch (t) { 246 | case LUA_TSTRING: 247 | return lua_tostring(L,index); 248 | case LUA_TNUMBER: 249 | sprintf(buffer,"[%lg]",lua_tonumber(L,index)); 250 | break; 251 | case LUA_TBOOLEAN: 252 | sprintf(buffer,"[%s]",lua_toboolean(L,index) ? "true" : "false"); 253 | break; 254 | case LUA_TNIL: 255 | sprintf(buffer,"[nil]"); 256 | break; 257 | default: 258 | sprintf(buffer,"[%s:%p]",lua_typename(L,t),lua_topointer(L,index)); 259 | break; 260 | } 261 | return buffer; 262 | } 263 | 264 | static void 265 | mark_table(lua_State *L, lua_State *dL, const void * parent, const char * desc, struct snapshot_params* args) { 266 | const void * t = readobject(L, dL, parent, desc); 267 | if (t == NULL) 268 | return; 269 | 270 | check_limit(L, args); 271 | 272 | bool weakk = false; 273 | bool weakv = false; 274 | if (lua_getmetatable(L, -1)) { 275 | lua_pushliteral(L, "__mode"); 276 | lua_rawget(L, -2); 277 | if (lua_isstring(L,-1)) { 278 | const char *mode = lua_tostring(L, -1); 279 | if (strchr(mode, 'k')) { 280 | weakk = true; 281 | } 282 | if (strchr(mode, 'v')) { 283 | weakv = true; 284 | } 285 | } 286 | lua_pop(L,1); 287 | 288 | luaL_checkstack(L, LUA_MINSTACK, NULL); 289 | mark_table(L, dL, t, "[metatable]", args); 290 | } 291 | 292 | lua_pushnil(L); 293 | while (lua_next(L, -2) != 0) { 294 | if (weakv) { 295 | lua_pop(L,1); 296 | } else { 297 | char temp[32]; 298 | const char * desc = keystring(L, -2, temp); 299 | mark_object(L, dL, t , desc, args); 300 | } 301 | if (!weakk) { 302 | lua_pushvalue(L,-1); 303 | mark_object(L, dL, t , "[key]", args); 304 | } 305 | } 306 | 307 | lua_pop(L,1); 308 | } 309 | 310 | static void 311 | mark_string(lua_State *L, lua_State *dL, const void * parent, const char *desc) { 312 | const void* t = readobject(L, dL, parent, desc); 313 | if(t == NULL) 314 | return; 315 | lua_pop(L,1); 316 | } 317 | 318 | static void 319 | mark_userdata(lua_State *L, lua_State *dL, const void * parent, const char *desc, struct snapshot_params* args) { 320 | const void * t = readobject(L, dL, parent, desc); 321 | if (t == NULL) 322 | return; 323 | 324 | check_limit(L, args); 325 | 326 | if (lua_getmetatable(L, -1)) { 327 | mark_table(L, dL, t, "[metatable]", args); 328 | } 329 | 330 | lua_getuservalue(L,-1); 331 | if (lua_isnil(L,-1)) { 332 | lua_pop(L,2); 333 | } else { 334 | mark_object(L, dL, t, "[uservalue]", args); 335 | lua_pop(L,1); 336 | } 337 | } 338 | 339 | static void 340 | mark_function(lua_State *L, lua_State *dL, const void * parent, const char *desc, struct snapshot_params* args) { 341 | const void * t = readobject(L, dL, parent, desc); 342 | if (t == NULL) 343 | return; 344 | 345 | check_limit(L, args); 346 | 347 | mark_function_env(L,dL,t, args); 348 | int i; 349 | for (i=1;;i++) { 350 | const char *name = lua_getupvalue(L,-1,i); 351 | if (name == NULL) 352 | break; 353 | mark_object(L, dL, t, name[0] ? name : "[upvalue]", args); 354 | } 355 | if (lua_iscfunction(L,-1)) { 356 | lua_pop(L,1); 357 | } else { 358 | lua_Debug ar; 359 | lua_getinfo(L, ">S", &ar); 360 | luaL_Buffer b; 361 | luaL_buffinit(dL, &b); 362 | luaL_addstring(&b, ar.short_src); 363 | char tmp[16]; 364 | sprintf(tmp,":%d",ar.linedefined); 365 | luaL_addstring(&b, tmp); 366 | luaL_pushresult(&b); 367 | lua_rawsetp(dL, SOURCE, t); 368 | } 369 | } 370 | 371 | static void 372 | mark_thread(lua_State *L, lua_State *dL, const void * parent, const char *desc, struct snapshot_params* args) { 373 | const void * t = readobject(L, dL, parent, desc); 374 | if (t == NULL) 375 | return; 376 | 377 | check_limit(L, args); 378 | 379 | int level = 0; 380 | lua_State *cL = lua_tothread(L,-1); 381 | if (cL == L) { 382 | level = 1; 383 | } else { 384 | // mark stack 385 | int top = lua_gettop(cL); 386 | luaL_checkstack(cL, 1, NULL); 387 | int i; 388 | char tmp[16]; 389 | for (i=0;i=0) { 403 | char tmp[16]; 404 | sprintf(tmp,":%d ",ar.currentline); 405 | luaL_addstring(&b, tmp); 406 | } 407 | 408 | int i,j; 409 | for (j=1;j>-1;j-=2) { 410 | for (i=j;;i+=j) { 411 | const char * name = lua_getlocal(cL, &ar, i); 412 | if (name == NULL) 413 | break; 414 | snprintf(tmp, sizeof(tmp), "%s{#%s:%d}",name,ar.short_src,ar.linedefined); 415 | mark_object(cL, dL, t, tmp, args); 416 | } 417 | } 418 | 419 | ++level; 420 | } 421 | luaL_pushresult(&b); 422 | lua_rawsetp(dL, SOURCE, t); 423 | lua_pop(L,1); 424 | } 425 | 426 | static void 427 | mark_object(lua_State *L, lua_State *dL, const void * parent, const char *desc, struct snapshot_params* args) { 428 | luaL_checkstack(L, LUA_MINSTACK, NULL); 429 | int t = lua_type(L, -1); 430 | #ifdef isshared 431 | if(check_shared(L, -1, t)) { 432 | lua_pop(L, 1); 433 | return; 434 | } 435 | #endif 436 | switch (t) { 437 | case LUA_TTABLE: 438 | mark_table(L, dL, parent, desc, args); 439 | break; 440 | case LUA_TUSERDATA: 441 | mark_userdata(L, dL, parent, desc, args); 442 | break; 443 | case LUA_TFUNCTION: 444 | mark_function(L, dL, parent, desc, args); 445 | break; 446 | case LUA_TTHREAD: 447 | mark_thread(L, dL, parent, desc, args); 448 | break; 449 | case LUA_TSTRING: 450 | mark_string(L, dL, parent, desc); 451 | break; 452 | default: 453 | lua_pop(L,1); 454 | break; 455 | } 456 | } 457 | 458 | static int 459 | count_table(lua_State *L, int idx) { 460 | int n = 0; 461 | lua_pushnil(L); 462 | while (lua_next(L, idx) != 0) { 463 | ++n; 464 | lua_pop(L,1); 465 | } 466 | return n; 467 | } 468 | 469 | static void 470 | gen_table_desc(lua_State *dL, luaL_Buffer *b, const void * parent, const char *desc) { 471 | char tmp[32]; 472 | size_t l = sprintf(tmp,"%p : ",parent); 473 | luaL_addlstring(b, tmp, l); 474 | luaL_addstring(b, desc); 475 | luaL_addchar(b, '\n'); 476 | } 477 | 478 | static size_t 479 | _table_size(Table* p) { 480 | size_t size = sizeof(*p); 481 | size_t tl = (p->lastfree == NULL)?(0):(sizenode(p)); 482 | size += tl*sizeof(Node); 483 | #if LUA_VERSION_NUM == 504 484 | size += _luaH_realasize(p)*sizeof(TValue); 485 | #else 486 | size += p->sizearray*sizeof(TValue); 487 | #endif 488 | return size; 489 | } 490 | 491 | static size_t 492 | _thread_size(struct lua_State* p) { 493 | size_t size = sizeof(*p); 494 | size += p->nci*sizeof(CallInfo); 495 | #ifdef stacksize 496 | #if LUA_VERSION_RELEASE_NUM < 50405 497 | size += stacksize(p)*sizeof(*p->stack); 498 | #else 499 | size += stacksize(p)*sizeof(*p->stack.p); 500 | #endif 501 | #else 502 | #if LUA_VERSION_RELEASE_NUM < 50405 503 | size += p->stacksize*sizeof(*p->stack); 504 | #else 505 | size += p->stacksize*sizeof(*p->stack.p); 506 | #endif 507 | #endif 508 | return size; 509 | } 510 | 511 | 512 | static size_t 513 | _userdata_size(Udata *p) { 514 | #if LUA_VERSION_NUM == 504 515 | int size = sizeudata(p->nuvalue, p->len); 516 | return size; 517 | #else 518 | int l = sizeudata(p); 519 | size_t size = sizeludata(l); 520 | return size; 521 | #endif 522 | } 523 | 524 | static size_t 525 | _cfunc_size(CClosure* p) { 526 | int n = (int)(p->nupvalues); 527 | size_t size = sizeCclosure(n); 528 | return size; 529 | } 530 | 531 | 532 | static size_t 533 | _lfunc_size(LClosure *p) { 534 | int n = (int)(p->nupvalues); 535 | size_t size = sizeLclosure(n); 536 | return size; 537 | } 538 | 539 | static size_t 540 | _lstring_size(const void* s) { 541 | #if LUA_VERSION_NUM == 504 542 | TString* ts = (TString*)(((char*)s) - offsetof(TString, contents)); 543 | #else 544 | TString* ts = (TString*)(((char*)s) - sizeof(UTString)); 545 | #endif 546 | size_t size = tsslen(ts); 547 | return size; 548 | } 549 | 550 | static void 551 | pdesc(lua_State *L, lua_State *dL, int idx, const char * typename) { 552 | lua_pushnil(dL); 553 | size_t size = 0; 554 | char buff_sz[128] = {0}; 555 | while (lua_next(dL, idx) != 0) { 556 | luaL_Buffer b; 557 | luaL_buffinit(L, &b); 558 | const void * key = lua_touserdata(dL, -2); 559 | switch(idx) { 560 | case FUNCTION: { 561 | lua_rawgetp(dL, SOURCE, key); 562 | if (lua_isnil(dL, -1)) { 563 | size = _cfunc_size((CClosure*)key); 564 | snprintf(buff_sz, sizeof(buff_sz), "{%zd}", size); 565 | luaL_addstring(&b,"cfunction"); 566 | luaL_addchar(&b,' '); 567 | luaL_addstring(&b, buff_sz); 568 | luaL_addchar(&b,'\n'); 569 | } else { 570 | size_t l = 0; 571 | size = _lfunc_size((LClosure*)key); 572 | snprintf(buff_sz, sizeof(buff_sz), "{%zd}", size); 573 | const char * s = lua_tolstring(dL, -1, &l); 574 | if(l==0) { 575 | s = "?"; 576 | l = 1; 577 | } 578 | luaL_addlstring(&b,s,l); 579 | luaL_addchar(&b,' '); 580 | luaL_addstring(&b, buff_sz); 581 | luaL_addchar(&b,'\n'); 582 | } 583 | lua_pop(dL, 1); 584 | }break; 585 | 586 | case THREAD: { 587 | lua_rawgetp(dL, SOURCE, key); 588 | size_t l = 0; 589 | size = _thread_size((struct lua_State*)key); 590 | snprintf(buff_sz, sizeof(buff_sz), "{%zd}", size); 591 | const char * s = lua_tolstring(dL, -1, &l); 592 | luaL_addstring(&b,"thread "); 593 | luaL_addlstring(&b,s,l); 594 | luaL_addchar(&b,' '); 595 | luaL_addstring(&b, buff_sz); 596 | luaL_addchar(&b,'\n'); 597 | lua_pop(dL, 1); 598 | }break; 599 | 600 | case TABLE: { 601 | size = _table_size((Table*)key); 602 | snprintf(buff_sz, sizeof(buff_sz), "{%zd}", size); 603 | luaL_addstring(&b, typename); 604 | luaL_addchar(&b,' '); 605 | luaL_addstring(&b, buff_sz); 606 | luaL_addchar(&b,'\n'); 607 | }break; 608 | 609 | case USERDATA: { 610 | Udata* p = (Udata*)key; 611 | size = _userdata_size(p); 612 | snprintf(buff_sz, sizeof(buff_sz), "{%zd}", size); 613 | luaL_addstring(&b, typename); 614 | luaL_addchar(&b,' '); 615 | luaL_addstring(&b, buff_sz); 616 | luaL_addchar(&b,'\n'); 617 | }break; 618 | 619 | case STRING: { 620 | size = _lstring_size(key); 621 | snprintf(buff_sz, sizeof(buff_sz), "{%zd}", size); 622 | luaL_addstring(&b, typename); 623 | luaL_addchar(&b,' '); 624 | luaL_addstring(&b, buff_sz); 625 | luaL_addchar(&b,'\n'); 626 | }break; 627 | 628 | default: { 629 | snprintf(buff_sz, sizeof(buff_sz), "{0}"); 630 | luaL_addstring(&b, typename); 631 | luaL_addchar(&b,' '); 632 | luaL_addstring(&b, buff_sz); 633 | luaL_addchar(&b,'\n'); 634 | }break; 635 | } 636 | lua_pushnil(dL); 637 | while (lua_next(dL, -2) != 0) { 638 | const void * parent = lua_touserdata(dL,-2); 639 | const char * desc = luaL_checkstring(dL,-1); 640 | gen_table_desc(dL, &b, parent, desc); 641 | lua_pop(dL,1); 642 | } 643 | luaL_pushresult(&b); 644 | lua_rawsetp(L, -2, key); 645 | lua_pop(dL,1); 646 | } 647 | } 648 | 649 | static void 650 | gen_result(lua_State *L, lua_State *dL) { 651 | int count = 0; 652 | count += count_table(dL, TABLE); 653 | count += count_table(dL, FUNCTION); 654 | count += count_table(dL, USERDATA); 655 | count += count_table(dL, THREAD); 656 | count += count_table(dL, STRING); 657 | lua_createtable(L, 0, count); 658 | pdesc(L, dL, TABLE, "table"); 659 | pdesc(L, dL, USERDATA, "userdata"); 660 | pdesc(L, dL, FUNCTION, "function"); 661 | pdesc(L, dL, THREAD, "thread"); 662 | pdesc(L, dL, STRING, "string"); 663 | } 664 | 665 | static int 666 | snapshot(lua_State *L) { 667 | int i; 668 | struct snapshot_params args = {0}; 669 | args.max_count = luaL_optinteger(L, 1, 0); 670 | lua_State *dL = luaL_newstate(); 671 | for (i=0;itop - 1); 708 | #else 709 | o = s2v(L->top.p - 1); 710 | #endif 711 | #else 712 | o = L->top - 1; 713 | #endif 714 | // const TValue *o = index2value(L, -1); 715 | p = (void*)uvalue(o); 716 | } else if (t == LUA_TSTRING) { 717 | p = (void*)lua_tostring(L, -1); 718 | } 719 | else { 720 | p = (void*)lua_topointer(L, -1); 721 | } 722 | lua_pushlightuserdata(L, p); 723 | return 1; 724 | } 725 | 726 | static void 727 | _objectsize(lua_State* L, int value_idx, int map_idx, int max_deep, int cur_deep, size_t* sz_p) { 728 | size_t size = 0; 729 | int t = lua_type(L, value_idx); 730 | if(cur_deep > max_deep) { 731 | return; 732 | } 733 | 734 | // check share object 735 | void* key = (void*)lua_topointer(L, value_idx); 736 | #ifdef isshared 737 | if(t == LUA_TSTRING || t == LUA_TTABLE) { 738 | GCObject* o = (GCObject*)key; 739 | if(isshared(o)) { 740 | return; 741 | } 742 | } 743 | #endif 744 | 745 | luaL_checkstack(L, LUA_MINSTACK, NULL); 746 | lua_pushlightuserdata(L, key); 747 | int mt = lua_gettable(L, map_idx); 748 | lua_pop(L, 1); 749 | if(mt != LUA_TNIL) { 750 | return; 751 | } 752 | 753 | lua_pushlightuserdata(L, key); 754 | lua_pushboolean(L, true); 755 | lua_settable(L, map_idx); 756 | switch(t) { 757 | case LUA_TSTRING: { 758 | key = (void*)lua_tostring(L, value_idx); 759 | size = _lstring_size(key); 760 | *sz_p += size; 761 | } break; 762 | 763 | case LUA_TFUNCTION: { 764 | if (is_lightcfunction(L, value_idx)) { 765 | lua_pop(L, 1); 766 | size = _cfunc_size((CClosure*)key); 767 | } else { 768 | size = _lfunc_size((LClosure*)key); 769 | } 770 | *sz_p += size; 771 | } break; 772 | 773 | case LUA_TTHREAD: { 774 | size = _thread_size((struct lua_State*)key); 775 | *sz_p += size; 776 | } break; 777 | 778 | case LUA_TTABLE: { 779 | size = _table_size((Table*)key); 780 | *sz_p += size; 781 | lua_pushnil(L); 782 | while(lua_next(L, value_idx) != 0) { 783 | int vidx = lua_gettop(L); 784 | int kidx = vidx-1; 785 | _objectsize(L, vidx, map_idx, max_deep, cur_deep+1, sz_p); 786 | _objectsize(L, kidx, map_idx, max_deep, cur_deep+1, sz_p); 787 | lua_pop(L, 1); 788 | } 789 | } break; 790 | } 791 | 792 | lua_pushlightuserdata(L, key); 793 | lua_pushnil(L); 794 | lua_settable(L, map_idx); 795 | } 796 | 797 | static int 798 | l_objsize(lua_State* L) { 799 | lua_newtable(L); 800 | int map_idx = lua_gettop(L); 801 | bool recursive = lua_toboolean(L, 2); 802 | size_t sz = 0; 803 | int max_deep = (recursive)?(128):(0); 804 | _objectsize(L, 1, map_idx, max_deep, 0, &sz); 805 | lua_pushinteger(L, sz); 806 | return 1; 807 | } 808 | 809 | int 810 | luaopen_snapshot(lua_State *L) { 811 | luaL_checkversion(L); 812 | luaL_Reg l[] = { 813 | {"snapshot", snapshot}, 814 | {"str2ud", l_str2lightuserdata}, 815 | {"ud2str", l_lightuserdata2str}, 816 | {"obj2addr", l_obj2addr}, 817 | {"objsize", l_objsize}, 818 | {NULL, NULL}, 819 | }; 820 | // lua_pushcfunction(L, snapshot); 821 | luaL_newlib(L, l); 822 | return 1; 823 | } 824 | --------------------------------------------------------------------------------