forked from peter4431/LuaSoar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdebugger.lua
3339 lines (2953 loc) · 133 KB
/
debugger.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- Debugger using DBGp protocol.
-------------------------------------------------------------------------------
-- The module returns a single init function which takes 6 parameters (IDEHOST, IDEPORT, IDEKEY, TRANSPORT, PLATFORM, WORKINGDIR).
--
-- IDEHOST: the host name or the ip address of the DBGP server (so your ide)
-- if HOST is nil, the DBGP_IDEHOST env var is used.
-- if the env var is nil, the default value '127.0.0.1' is used.
--
-- IDEPORT: the port of the DBGP server (must be configure in the IDE)
-- if PORT is nil, the DBGP_IDEPORT env var is used.
-- if the env var is nil, the default value '10000' is used.
--
-- IDEIDEKEY: a string which is used as session key
-- if IDEKEY is nil, the DBGP_IDEKEY env var is used.
-- if the env var is nil, the default value 'luaidekey' is used.
--
-- TRANSPORT: (advanced optional parameter) the module name of which implement the transport interface used to do the connection with the server.
-- by default the debugger use an internal implementation based on luasocket, but if can not use it, you could implement or use another transport layer implementation.
-- if TRANSPORT is nil, the DBGP_TRANSPORT env var is used.
-- if the env var is nil, the default value 'debugger.transport.luasocket' is used : this is the default implementation based on luasocket.
--
-- PLATFORM: (advanced optional parameter) 'unix' or 'win32' string which define the kind of platform on which the program to debug is executed.
-- by default the debugger will try to guess it and surely success, if for some reasons it fails you could help it by precise the execution platform.
-- if PLATFORM is nil, the DBGP_PLATFORM env var is used.
-- if the env var is nil, the debugger will try to guess it.
--
-- WORKINGDIR: (advanced optional parameter) the working directory in which the program to debug is executed.
-- by default the debugger will try to guess it and surely success, if for some reasons it fails you could help it by precise the working directory.
-- if WORKINGDIR is nil, the DBGP_WORKINGDIR env var is used.
-- if the env var is nil, the debugger will try to guess it.
--
-------------------------------------------------------------------------------
-- Known Issues:
-- * Functions cannot be created using the debugger and then called in program because their environment is mapped directly to
-- a debugger internal structure which cannot be persisted (i.e. used outside of the debug_hook).
-- * The DLTK client implementation does not handle context for properties. As a workaround, the context is encoded into the
-- fullname attribute of each property and is used likewise in property_get commands. The syntax is "<context ID>|<full name>"
-- * Dynamic code (compiled with load or loadstring) is not handled (the debugger will step over it, like C code)
-- Design notes:
-- * The whole debugger state is kept in a (currently) unique session table in order to ease eventual adaptation to a multi-threaded
-- model, as DBGp needs one connection per thread.
-- * Full names of properties are base64 encoded because they can contain arbitrary data (spaces, escape characters, ...), this makes
-- command parsing munch easier and faster
-- * This debugger supports asynchronous commands: any command can be done at any time, but some of them (continuations) can lead to
-- inconsistent states. In addition, this have a quite big overhead (~66%), if performance is an issue, a custom command to disable
-- async mode could be done.
-- * All commands are implemented in table commands, see this comments on this table to additional details about commands implementation
-- * The environments in which are evaluated user code (property_* and eval commands, conditional breakpoints, ...) is a read/write
-- mapping of the local environment of a given stack level (can be accessed with variable names). See Context for additional details.
-- Context instantiation is pooled inside a debugging loop with ContextManager (each stack level is instantiated only once).
-- * Output redirection is done by redefining print and some values inside the io table. See "Output redirection handling" for details.
-- Todo list:
-- * Override I/O in init function instead of on module loading.
-- * Allow to break programatically (debugger.break()).
-- * Break-on-error feature (break if an error is thrown and there is no pcall in stack to handle it).
-- * Use new 5.2 facilities to provide informations about function (arguments names, vararg, ...)
-- * Allow to see ... content for vararg functions (5.2 only)
-- * Inspect LuaJIT C data (http://lua-users.org/lists/lua-l/2011-02/msg01012.html)-- /!\ This file is auto-generated. Do not alter manually /!\
--------------------------------------------------------------------------------
-- Submodules body
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local android_source_patch = function(info_source)
if type(info_source) == "string" then
if string.sub(info_source,0,string.len("assets/")) == "assets/" then
info_source = string.sub(info_source,string.len("assets/")+1)
end
else
if string.sub(info_source.source,0,string.len("assets/")) == "assets/" then
info_source.source = string.sub(info_source.source,string.len("assets/")+1)
end
end
return info_source
end
-- Module debugger.transport.apr
package.preload["debugger.transport.apr"] = function(...)
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- Apache Portable Runtime backend for DBGP debugger.
-------------------------------------------------------------------------------
local apr = require "apr"
-- base 64 wrapping
function b64_wrap(src)
local t = {}
local b64_src = mime.b64(src)
for i=1, #b64_src, 76 do t[#t+1] = b64_src:sub(i, i+75).."\r\n" end
return table.concat(t)
end
-- implements a subset of LuaSocket API using APR
local SOCKET_MT = {
connect = function(self, address, port) return self.skt:connect(address, port) end,
receive = function(self, n) return self.skt:read(n) end, -- only numeric read is used
send = function(self, data) print("skt:write") return self.skt:write(data) end,
close = function(self) return self.skt:close() end,
settimeout = function(self, sec)
if sec == nil then self.skt:timeout_set(true)
elseif sec == 0 then self.skt:timeout_set(false)
else self.skt:timeout_set(math.floor(sec * 1000000)) end
end
}
SOCKET_MT.__index = SOCKET_MT
return {
create = function()
local skt, err = apr.socket_create('tcp')
if not skt then return nil, err end
return setmetatable({skt = skt}, SOCKET_MT)
end,
sleep = apr.sleep, -- exact same API as LuaSocket
-- Base64 related functions
--- Encodes a string into Base64 with line wrapping
-- @param data (string) data to encode
-- @return base64 encoded string
b64 = function(data)
t = {}
local b64_data = apr.base64_encode(data)
for i=1, #b64_data, 76 do t[#t+1] = b64_data:sub(i, i+75).."\r\n" end
return table.concat(t)
end,
--- Encodes a string into Base64, without any extra parsing (wrapping, ...)
-- @param data (string) data to encode
-- @return decoded string
rawb64 = apr.base64_encode,
--- Decodes base64 data
-- @param data (string) base64 encoded data
-- @return decoded string
unb64 = apr.base64_decode,
}
end
--------------------------------------------------------------------------------
-- End of moduledebugger.transport.apr
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Module debugger.transport.luasocket
package.preload["debugger.transport.luasocket"] = function(...)
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- LuaSocket backend for DBGP debugger.
-------------------------------------------------------------------------------
-- in order to be as lightweight as possible with Luasocket, core API is used
-- directly (to no add yet another layer)
--FIXME: remove this hack as soon as luasocket officially support 5.2
if _VERSION == "Lua 5.2" then
table.getn = function(t) return t and #t end
end
local socket = require "socket"
local mime = require "mime"
local ltn12 = require "ltn12"
local reg = debug.getregistry()
return {
create = socket.tcp,
sleep = socket.sleep,
-- Base64 related functions
--- Encodes a string into Base64 with line wrapping
-- @param data (string) data to encode
-- @return base64 encoded string
b64 = function(data)
local filter = ltn12.filter.chain(mime.encode("base64"), mime.wrap("base64"))
local sink, output = ltn12.sink.table()
ltn12.pump.all(ltn12.source.string(data), ltn12.sink.chain(filter, sink))
return table.concat(output)
end,
--- Encodes a string into Base64, without any extra parsing (wrapping, ...)
-- @param data (string) data to encode
-- @return decoded string
rawb64 = function(data)
return (mime.b64(data)) -- first result of the low-level function is fine here
end,
--- Decodes base64 data
-- @param data (string) base64 encoded data
-- @return decoded string
unb64 = function(data)
return (mime.unb64(data)) -- first result of the low-level function is fine here
end,
}
end
--------------------------------------------------------------------------------
-- End of moduledebugger.transport.luasocket
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Module debugger.transport.luasocket_sched
package.preload["debugger.transport.luasocket_sched"] = function(...)
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- LuaSocket with LuaSched backend for DBGP debugger.
-------------------------------------------------------------------------------
-- As LuaShed totally hides blocking functions, this module MUST be loaded on the very start of the program
-- (before loading sched) to catch references to blocking functions.
local socketcore = require"socket.core"
local debug = require "debug"
local reg = debug.getregistry()
local blockingcreate = socketcore.tcp
local blockingsleep = socketcore.sleep
local blockingconnect = reg["tcp{master}"].__index.connect
local blockingreceive = reg["tcp{client}"].__index.receive
local blockingsend = reg["tcp{client}"].__index.send
local blockingsettimeout = reg["tcp{master}"].__index.settimeout
local blockingclose = reg["tcp{master}"].__index.close
-- we cannot set a new metatable directly on socket object, so wrap it into a new table
-- and forward all calls.
local blockingtcp = {
connect = function(self, address, port) return blockingconnect(self.skt, address, port) end,
receive = function(self, n) return blockingreceive(self.skt, n) end,
send = function(self, data) return blockingsend(self.skt, data) end,
settimeout = function(self, sec) return blockingsettimeout(self.skt, sec) end,
close = function(self) return blockingclose(self.skt) end,
}
blockingtcp.__index = blockingtcp
local mime = require "mime"
local ltn12 = require "ltn12"
-- verify that the socket function are the real ones and not sched not blocking versions
assert(debug.getinfo(blockingcreate, "S").what == "C", "The debugger needs the real socket functions !")
-- cleanup the package.loaded table (socket.core adds socket field into it)
package.loaded.socket = nil
return {
create = function() return setmetatable({ skt = blockingcreate() }, blockingtcp) end,
sleep = blockingsleep,
-- Base64 related functions
--- Encodes a string into Base64 with line wrapping
-- @param data (string) data to encode
-- @return base64 encoded string
b64 = function(data)
local filter = ltn12.filter.chain(mime.encode("base64"), mime.wrap("base64"))
local sink, output = ltn12.sink.table()
ltn12.pump.all(ltn12.source.string(data), ltn12.sink.chain(filter, sink))
return table.concat(output)
end,
--- Encodes a string into Base64, without any extra parsing (wrapping, ...)
-- @param data (string) data to encode
-- @return decoded string
rawb64 = function(data)
return (mime.b64(data)) -- first result of the low-level function is fine here
end,
--- Decodes base64 data
-- @param data (string) base64 encoded data
-- @return decoded string
unb64 = function(data)
return (mime.unb64(data)) -- first result of the low-level function is fine here
end,
}
end
--------------------------------------------------------------------------------
-- End of moduledebugger.transport.luasocket_sched
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Module debugger.commands
package.preload["debugger.commands"] = function(...)
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- Commands handlers for DBGp protocol.
-------------------------------------------------------------------------------
-- Debugger command functions. Each function handle a different command.
-- A command function is called with 3 arguments
-- 1. the debug session instance
-- 2. the command arguments as table
-- 3. the command data, if any
-- The result is either :
-- * true (or any value evaluated to true) : the debugger will resume the execution of the application (continuation command)
-- * false : only in async mode, the debugger WILL wait for further commands instead of continuing (typically, break command)
-- * nil/no return : in sync mode, the debugger will wait for another command. In async mode the debugger will continue the execution
local cowrap, coyield = coroutine.wrap, coroutine.yield
local debug = require "debug"
local core = require "debugger.core"
local dbgp = require "debugger.dbgp"
local util = require "debugger.util"
local platform = require "debugger.platform"
local introspection = require "debugger.introspection"
local context = require "debugger.context"
local log = util.log
local M = { } -- command handlers table
--- Gets the coroutine behind an id
-- Throws errors on unknown identifiers
-- @param coro_id (string or nil) Coroutine identifier or nil (current coroutine)
-- @return Coroutine instance or nil (if coro_id was nil or if coroutine is the current coroutine)
local function get_coroutine(self, coro_id)
if coro_id then
local coro = dbgp.assert(399, core.active_coroutines.from_id[tonumber(coro_id)], "No such coroutine")
dbgp.assert(399, coroutine.status(coro) ~= "dead", "Coroutine is dead")
if coro ~= self.coro[1] then return util.ForeignThread(coro) end
end
return self.coro
end
M["break"] = function(self, args)
self.state = "break"
-- send response to previous command
core.previous_context_response(self)
-- and then response to break command itself
dbgp.send_xml(self.skt, { tag = "response", attr = { command = "break", transaction_id = args.i, success = 1 } } )
return false
end
function M.status(self, args)
dbgp.send_xml(self.skt, { tag = "response", attr = {
command = "status",
reason = "ok",
status = self.state,
transaction_id = args.i } } )
end
function M.stop(self, args)
dbgp.send_xml(self.skt, { tag = "response", attr = {
command = "stop",
reason = "ok",
status = "stopped",
transaction_id = args.i } } )
self.skt:close()
os.exit(1)
end
function M.feature_get(self, args)
local name = args.n
local response = util.features[name] or (not not M[name])
dbgp.send_xml(self.skt, { tag = "response", attr = {
command = "feature_get",
feature_name = name,
supported = response and "1" or "0",
transaction_id = args.i },
tostring(response) } )
end
function M.feature_set(self, args)
local name, value = args.n, args.v
local success = pcall(function() util.features[name] = value end)
dbgp.send_xml(self.skt, { tag = "response", attr = {
command = "feature_set",
feature = name,
success = success and 1 or 0,
transaction_id = args.i
} } )
end
function M.typemap_get(self, args)
local function gentype(name, type, xsdtype)
return { tag = "map", atts = { name = name, type = type, ["xsi:type"] = xsdtype } }
end
dbgp.send_xml(self.skt, { tag = "response", attr = {
command = "typemap_get",
transaction_id = args.i,
["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance",
["xmlns:xsd"] = "http://www.w3.org/2001/XMLSchema",
},
gentype("nil", "null"),
gentype("boolean", "bool", "xsd:boolean"),
gentype("number", "float", "xsd:float"),
gentype("string", "string", "xsd:string"),
gentype("function", "resource"),
gentype("userdata", "resource"),
gentype("thread", "resource"),
gentype("table", "hash"),
gentype("sequence", "array"), -- artificial type to represent sequences (1-n continuous indexes)
gentype("multival", "array"), -- used to represent return values
} )
end
function M.run(self) return true end
function M.step_over(self)
core.events.register("over")
return true
end
function M.step_out(self)
core.events.register("out")
return true
end
function M.step_into(self)
core.events.register("into")
return true
end
function M.eval(self, args, data)
log("DEBUG", "Going to eval "..data)
local result, err, success
local env = self.stack(self.coro, 0)
-- first, try to load as expression
-- DBGp does not support stack level here, see http://bugs.activestate.com/show_bug.cgi?id=81178
local func, err = util.loadin("return "..data, env)
-- if it is not an expression, try as statement (assignment, ...)
if not func then
func, err = util.loadin(data, env)
end
if func then
success, result = pcall(function() return introspection.Multival(func()) end)
if not success then err = result end
end
local response = { tag = "response", attr = { command = "eval", transaction_id = args.i } }
if not err then
local nresults = result.n
if nresults == 1 then result = result[1] end
-- store result for further use (property_*)
-- TODO: this could be optimized: this is only used for Expressions view and totally useless for interactive console,
-- so storing result or not could be set by an argument
local idx
if nresults > 0 then
local cache = env[context.Context[-1]]
idx = #cache + 1
cache[idx] = result
end
-- As of Lua 5.1, the maximum stack size (and result count) is 8000, this limit is used to fit all results in one page
response[1] = introspection.make_property(-1, result, idx or "", nil, 1, 8000, 0, nil)
response.attr.success = 1
else
response.attr.success = 0
response[1] = dbgp.make_error(206, err)
end
dbgp.send_xml(self.skt, response)
end
function M.breakpoint_set(self, args, data)
log("DEBUG","breakpoint_set:",args.f)
if args.o and not core.breakpoints.hit_conditions[args.o] then dbgp.error(200, "Invalid hit_condition operator: "..args.o) end
local filename, lineno = args.f, tonumber(args.n)
local bp = {
type = args.t,
state = args.s or "enabled",
temporary = args.r == "1", -- "0" or nil makes this property false
hit_count = 0,
filename = filename,
lineno = lineno,
hit_value = tonumber(args.h or 0),
hit_condition = args.o or ">=",
}
if args.t == "conditional" then
bp.expression = data
-- the expression is compiled only once
bp.condition = dbgp.assert(207, loadstring("return (" .. data .. ")"))
elseif args.t ~= "line" then dbgp.error(201, "BP type " .. args.t .. " not yet supported") end
local bpid = core.breakpoints.insert(bp)
dbgp.send_xml(self.skt, { tag = "response", attr = { command = "breakpoint_set", transaction_id = args.i, state = bp.state, id = bpid ,filename=bp.filename,line=lineno} } )
end
function M.breakpoint_get(self, args)
dbgp.send_xml(self.skt, { tag = "response",
attr = { command = "breakpoint_get", transaction_id = args.i },
dbgp.assert(205, core.breakpoints.get_xml(tonumber(args.d))) })
end
function M.breakpoint_list(self, args)
local bps = { tag = "response", attr = { command = "breakpoint_list", transaction_id = args.i } }
for id, bp in pairs(core.breakpoints.get()) do bps[#bps + 1] = core.breakpoints.get_xml(id) end
dbgp.send_xml(self.skt, bps)
end
function M.breakpoint_update(self, args)
local bp = core.breakpoints.get(tonumber(args.d))
if not bp then dbgp.error(205, "No such breakpint "..args.d) end
if args.o and not core.breakpoints.hit_conditions[args.o] then dbgp.error(200, "Invalid hit_condition operator: "..args.o) end
local response = { tag = "response", attr = { command = "breakpoint_update", transaction_id = args.i } }
bp.state = args.s or bp.state
bp.lineno = tonumber(args.n or bp.lineno)
bp.hit_value = tonumber(args.h or bp.hit_value)
bp.hit_condition = args.o or bp.hit_condition
dbgp.send_xml(self.skt, response)
end
function M.breakpoint_remove(self, args)
local response = { tag = "response", attr = { command = "breakpoint_remove", transaction_id = args.i } }
if not core.breakpoints.remove(tonumber(args.d)) then dbgp.error(205, "No such breakpint "..args.d) end
dbgp.send_xml(self.skt, response)
end
function M.stack_depth(self, args)
local depth = 0
local coro = get_coroutine(self, args.o)
for level = 0, math.huge do
local info = coro:getinfo(level, "St")
if not info then break end -- end of stack
depth = depth + 1
if info.istailcall then depth = depth + 1 end -- a 'fake' level is added in that case
if info.what == "main" then break end -- levels below main chunk are not interesting
end
dbgp.send_xml(self.skt, { tag = "response", attr = { command = "stack_depth", transaction_id = args.i, depth = depth} } )
end
function M.stack_get(self, args) -- TODO: dynamic code
-- special URIs to identify unreachable stack levels
local what2uri = {
tail = "tailreturn:/",
C = "ccode:/",
}
local function make_level(info, level)
local attr = { level = level, where = info.name, type="file" }
local uri = platform.get_uri(info.source)
if uri and info.currentline then -- reachable level
attr.filename = uri
attr.lineno = info.currentline
else
attr.filename = what2uri[info.what] or "unknown:/"
attr.lineno = -1
end
return { tag = "stack", attr = attr }
end
local node = { tag = "response", attr = { command = "stack_get", transaction_id = args.i} }
local coro = get_coroutine(self, args.o)
if args.d then
local stack_level = tonumber(args.d)
node[#node+1] = make_level(coro:getinfo(stack_level, "nSl"), stack_level)
else
for i=0, math.huge do
local info = coro:getinfo(i, "nSlt")
if not info then break end
node[#node+1] = make_level(info, i)
-- add a fake level of stack for tail calls (tells user that the function has not been called directly)
if info.istailcall then
node[#node+1] = { tag = "stack", attr = { level=i, type="file", filename="tailreturn:/", lineno=-1 } }
end
if info.what == "main" then break end -- levels below main chunk are not interesting
end
end
dbgp.send_xml(self.skt, node)
end
--- Lists all active coroutines.
-- Returns a list of active coroutines with their id (an arbitrary string) to query stack and properties. The id is
-- guaranteed to be unique and stable for all coroutine life (they can be reused as long as coroutine exists).
-- Others commands such as stack_get or property_* commands takes an additional -o switch to query a particular cOroutine.
-- If the switch is not given, running coroutine will be used.
-- In case of error on coroutines (most likely coroutine not found or dead), an error 399 is thrown.
-- Note there is an important limitation due to Lua 5.1 coroutine implementation: you cannot query main "coroutine" from
-- another one, so main coroutine is not in returned list (this will change with Lua 5.2).
--
-- This is a non-standard command. The returned XML has the following strucuture:
-- <response command="coroutine_list" transaction_id="0">
-- <coroutine name="<some printtable name>" id="<coroutine id>" running="0|1" />
-- ...
-- </response>
function M.coroutine_list(self, args)
local sdsd = self.coro[1]
local coroutines = { tag = "response", attr = { command = "coroutine_list", transaction_id = args.i } }
-- as any operation on main coroutine will fail, it is not yet listed
-- coroutines[1] = { name = "coroutine", attr = { id = 0, name = "main", sdsd = (sdsd == nil) and "1" or "0" } }
for id, coro in pairs(core.active_coroutines.from_id) do
if id ~= "n" then
coroutines[#coroutines + 1] = { tag = "coroutine", attr = { id = id, name = tostring(coro), running = (coro == running) and "1" or "0" } }
end
end
dbgp.send_xml(self.skt, coroutines)
end
function M.context_names(self, args)
local coro = get_coroutine(self, args.o)
local level = tonumber(args.d or 0)
local info = coro:getinfo(level, "f") or dbgp.error(301, "No such stack level "..tostring(level))
-- All contexts are always passed, even if empty. This is how DLTK expect context, what about others ?
local contexts = {
tag = "response", attr = { command = "context_names", transaction_id = args.i },
{ tag = "context", attr = { name = "Local", id = 0 } },
{ tag = "context", attr = { name = "Upvalue", id = 2 } },
{ tag = "context", attr = { name = "Global", id = 1 } },
}
dbgp.send_xml(self.skt, contexts)
end
function M.context_get(self, args)
local cxt_num = tonumber(args.c or 0)
local cxt_id = context.Context[cxt_num] or dbgp.error(302, "No such context: "..tostring(cxt_num))
local level = tonumber(args.d or 0)
local coro = get_coroutine(self, args.o)
local cxt = self.stack(coro, level)
local properties = { tag = "response", attr = { command = "context_get", transaction_id = args.i, context = context} }
-- iteration over global is different (this could be unified in Lua 5.2 thanks to __pairs metamethod)
for name, val in (cxt_num == 1 and next or getmetatable(cxt[cxt_id]).iterator), cxt[cxt_id], nil do
-- the DBGp specification is not clear about the depth of a context_get, but a recursive get could be *really* slow in Lua
properties[#properties + 1] = introspection.make_property(cxt_num, val, name, nil, 0, util.features.max_children, 0,
util.features.max_data, cxt_num ~= 1)
end
dbgp.send_xml(self.skt, properties)
end
-------------------------------------------------------------------------------
-- Property_* commands
-------------------------------------------------------------------------------
-- This in the environment in which properties are get or set.
-- It notably contain a collection of proxy table which handle transparentely get/set operations on special fields
-- and the cache of complex keys.
local property_evaluation_environment = {
key_cache = introspection.key_cache,
metatable = setmetatable({ }, {
__index = function(self, tbl) return getmetatable(tbl) end,
__newindex = function(self, tbl, mt) return setmetatable(tbl, mt) end,
}),
environment = util.eval_env,
}
-- to allows to be set as metatable
property_evaluation_environment.__index = property_evaluation_environment
function M.property_get(self, args)
--TODO BUG ECLIPSE TOOLSLINUX-99 352316
log("DEBUG","name:"..util.unb64(args.n))
local cxt_num, name = assert(util.unb64(args.n):match("^(%-?%d+)|(.*)$"))
cxt_num = tonumber(args.c or cxt_num)
local cxt_id = context.Context[cxt_num] or dbgp.error(302, "No such context: "..tostring(cxt_num))
local level = tonumber(args.d or 0)
local coro = get_coroutine(self, args.o)
local size = tonumber(args.m or util.features.max_data)
if size < 0 then size = nil end -- call from property_value
local page = tonumber(args.p or 0)
local cxt = self.stack(coro, level)
local chunk = dbgp.assert(206, util.loadin("return "..name, property_evaluation_environment))
local prop = select(2, dbgp.assert(300, pcall(chunk, cxt[cxt_id])))
local response = introspection.make_property(cxt_num, prop, name, name, util.features.max_depth, util.features.max_children, page, size)
-- make_property is not able to flag special variables as such when they are at root of property
-- special variables queries are in the form "<proxy name>[(...)[a][b]<...>]"
-- TODO: such parsing is far from perfect
if name:match("^[%w_]+%[.-%b[]%]$") == name then response.attr.type = "special" end
dbgp.send_xml(self.skt, { tag = "response",
attr = { command = "property_get", transaction_id = args.i, context = context},
response } )
end
function M.property_value(self, args)
args.m = -1
M.property_get(self, args)
end
function M.property_set(self, args, data)
local cxt_num, name = assert(util.unb64(args.n):match("^(%-?%d+)|(.*)$"))
cxt_num = tonumber(args.c or cxt_num)
local cxt_id = context.Context[cxt_num] or dbgp.error(302, "No such context: "..tostring(cxt_num))
local level = tonumber(args.d or 0)
local coro = get_coroutine(self, args.o)
local cxt = self.stack(coro, level)
-- evaluate the new value in the local context
local value = select(2, dbgp.assert(206, pcall(dbgp.assert(206, util.loadin("return "..data, cxt)))))
local chunk = dbgp.assert(206, util.loadin(name .. " = value", setmetatable({ value = value }, property_evaluation_environment)))
dbgp.assert(206, pcall(chunk, cxt[cxt_id]))
dbgp.send_xml(self.skt, { tag = "response", attr = { success = 1, transaction_id = args.i } } )
end
--TODO dynamic code handling
-- The DBGp specification is not clear about the line number meaning, this implementation is 1-based and numbers are inclusive
function M.source(self, args)
local path
if args.f then
path = platform.get_path(args.f)
else
path = self.coro:getinfo(0, "S").source
assert(path:sub(1,1) == "@")
path = path:sub(2)
end
local file, err = io.open(path)
if not file then dbgp.error(100, err, { success = 0 }) end
-- Try to identify compiled files
if file:read(1) == "\033" then dbgp.error(100, args.f.." is bytecode", { success = 0 }) end
file:seek("set", 0)
local srclines = { }
local beginline, endline, currentline = tonumber(args.b or 0), tonumber(args.e or math.huge), 0
for line in file:lines() do
currentline = currentline + 1
if currentline >= beginline and currentline <= endline then
srclines[#srclines + 1] = line
elseif currentline >= endline then break end
end
file:close()
srclines[#srclines + 1] = "" -- to add a trailing \n
dbgp.send_xml(self.skt, { tag = "response",
attr = { command = "source", transaction_id = args.i, success = 1},
util.b64(table.concat(srclines, "\n")) })
end
-- Factory for both stdout and stderr commands, change file descriptor in io
local function output_command_handler_factory(mode)
return function(self, args)
if args.c == "0" then -- disable
io[mode] = io.base[mode]
else
io[mode] = setmetatable({ skt = self.skt, mode = mode }, args.c == "1" and core.copy_output or core.redirect_output)
end
dbgp.send_xml(self.skt, { tag = "response", attr = { command = mode, transaction_id = args.i, success = "1" } } )
end
end
M.stdout = output_command_handler_factory("stdout")
M.stderr = output_command_handler_factory("stderr")
return M
end
--------------------------------------------------------------------------------
-- End of moduledebugger.commands
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Module debugger.context
package.preload["debugger.context"] = function(...)
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- Context handling: allows to evaluate code snippets in the context of a function
-------------------------------------------------------------------------------
local M = { }
local dbgp = require "debugger.dbgp"
local util = require "debugger.util"
-- make unique object to access contexts
local LOCAL, UPVAL, GLOBAL, EVAL, STORE, HANDLE = {}, {}, {}, {}, {}, {}
local getglobals
if _VERSION == "Lua 5.1" then
getglobals = function(f) return getfenv(f) end
elseif _VERSION == "Lua 5.2" then
getglobals = function(f, cxt)
-- 'global' environment: this is either the local _ENV or upvalue _ENV. A special case happen when a
-- function does not reference any global variable: the upvalue _ENV may not exist at all. In this case,
-- global environment is not relevant so it is fixed to an empty table. Another solution would be to set it
-- to the environment from above stack level but it would require some overhead (especially if multiple
-- levels must be instantiated)
if cxt[LOCAL][STORE]["_ENV"] then return cxt[LOCAL]["_ENV"]
elseif cxt[UPVAL][STORE]["_ENV"] then return cxt[UPVAL]["_ENV"]
else return { } end
end
end
--- Captures variables for given stack level. The capture contains local, upvalues and global variables.
-- The capture can be seen as a proxy table to the stack level: any value can be queried or set no matter
-- it is a local or an upvalue.
-- The individual local and upvalues context are also available and can be queried and modified with indexed notation too.
-- These objects are NOT persistant and must not be used outside the debugger loop which instanciated them !
M.Context = {
-- Context identifiers can be accessed by their DBGp context ID
[0] = LOCAL,
[1] = GLOBAL, -- DLTK internal ID for globals is 1
[2] = UPVAL,
-- EVAL is used to keep results from eval in cache in order to browse or modify them, results are stored as sequence
[-1] = EVAL,
STORE = STORE,
-- gets a variable by name with correct handling of Lua scope chain
-- the or chain does not work here beacause __index metamethod would raise an error instead of returning nil
__index = function(self, k)
if self[LOCAL][STORE][k] then return self[LOCAL][k]
elseif self[UPVAL][STORE][k] then return self[UPVAL][k]
else return self[GLOBAL][k] end
end,
__newindex = function(self, k, v)
if self[LOCAL][STORE][k] then self[LOCAL][k] = v
elseif self[UPVAL][STORE][k] then self[UPVAL][k] = v
else self[GLOBAL][k] = v end
end,
-- debug only !!
__tostring = function(self)
local buf = { "Locals: \n" }
for k,v in pairs(self[LOCAL][STORE]) do
buf[#buf+1] = "\t"..tostring(k).."("..tostring(v)..")="..tostring(self[LOCAL][k]).."\n"
end
buf[#buf+1] = "Upvalues: \n"
for k,v in pairs(self[UPVAL][STORE]) do
buf[#buf+1] = "\t"..tostring(k).."("..tostring(v)..")="..tostring(self[UPVAL][k]).."\n"
end
return table.concat(buf)
end,
LocalContext = {
__index = function(self, k)
local index = self[STORE][k]
if not index then error("The local "..tostring(k).." does not exists.") end
local handle = self[HANDLE]
return select(2, handle.coro:getlocal(handle.level, index))
end,
__newindex = function(self, k, v)
local index = self[STORE][k]
if index then
local handle = self[HANDLE]
handle.coro:setlocal(handle.level, index, v)
else error("Cannot set local " .. k) end
end,
-- Lua 5.2 ready :)
--__pairs = function(self) return getmetatable(self).iterator, self, nil end,
iterator = function(self, prev)
local key, index = next(self[STORE], prev)
if key then return key, self[key] else return nil end
end,
},
UpvalContext = {
__index = function(self, k)
local index = self[STORE][k]
if not index then error("The local "..tostring(k).." does not exitsts.") end
return select(2, debug.getupvalue(self[HANDLE], index))
end,
__newindex = function(self, k, v)
local index = self[STORE][k]
if index then debug.setupvalue(self[HANDLE], index, v)
else error("Cannot set upvalue " .. k) end
end,
-- Lua 5.2 ready :)
-- __pairs = function(self) return getmetatable(self).iterator, self, nil end,
iterator = function(self, prev)
local key, index = next(self[STORE], prev)
if key then return key, self[key] else return nil end
end,
},
--- Context constructor
-- @param coro (util.*Thread instance) coroutine to map to
-- @param level (number) stack level do dump (script stack level)
new = function(cls, coro, level)
local locals, upvalues = {}, {}
if level < 0 then dbgp.error(301, "No such stack level: "..tostring(level)) end
local func = (coro:getinfo(level, "f") or dbgp.error(301, "No such stack level: "..tostring(level))).func
-- local variables
for i=1, math.huge do
local name, val = coro:getlocal(level, i)
if not name then break
elseif name:sub(1,1) ~= "(" then -- skip internal values
locals[name] = i
end
end
-- upvalues
for i=1, math.huge do
local name, val = debug.getupvalue(func, i)
if not name then break end
upvalues[name] = i
end
locals = setmetatable({ [STORE] = locals, [HANDLE] = { level = level, coro = coro } }, cls.LocalContext)
upvalues = setmetatable({ [STORE] = upvalues, [HANDLE] = func }, cls.UpvalContext)
local result = setmetatable({ [LOCAL] = locals, [UPVAL] = upvalues, [EVAL] = {} }, cls)
rawset(result, GLOBAL, getglobals(func, result))
return result
end,
}
--- Handle caching of all instantiated context.
-- Returns a function which takes 2 parameters: thread and stack level and returns the corresponding context. If this
-- context has been already queried there is no new instantiation. A ContextManager is valid only during the debug loop
-- on which it has been instantiated. References to a ContextManager must be lost after the end of debug loop (so
-- threads can be collected).
-- If a context cannot be instantiated, an 301 DBGP error is thrown.
function M.ContextManager()
local cache = { }
return function(thread, level)
-- the real coroutine is used as key (not the wrapped instance as its unicity is not guaranteed)
-- otherwise, true is used to identify current thread (as nil is not a valid table key)
local key = thread[1] or true
local thread_contexts = cache[key]
if not thread_contexts then
thread_contexts = { }
cache[key] = thread_contexts
end
local context = thread_contexts[level]
if not context then
context = M.Context:new(thread, level)
thread_contexts[level] = context
end
return context
end
end
return M
end
--------------------------------------------------------------------------------
-- End of moduledebugger.context
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Module debugger.dbgp
package.preload["debugger.dbgp"] = function(...)
-------------------------------------------------------------------------------
-- Copyright (c) 2011-2012 Sierra Wireless and others.
-- All rights reserved. This program and the accompanying materials
-- are made available under the terms of the Eclipse Public License v1.0
-- which accompanies this distribution, and is available at
-- http://www.eclipse.org/legal/epl-v10.html
--
-- Contributors:
-- Sierra Wireless - initial API and implementation
-------------------------------------------------------------------------------
-- DBGp protocol utility function (parsing, error handling, XML generation).
-------------------------------------------------------------------------------
local util = require "debugger.util"
local error, setmetatable, type, pairs, ipairs, tostring, tconcat =
error, setmetatable, type, pairs, ipairs, tostring, table.concat
local M = { }