-
Notifications
You must be signed in to change notification settings - Fork 2
/
erlang-trace.lua
249 lines (220 loc) · 8.34 KB
/
erlang-trace.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
-- Copyright 2017 Magnus Henoch
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- Need to ensure that erlterm.lua is loaded first
dofile(USER_DIR .. "plugins/erlterm.lua")
-- Protocol dissector
local erlang_trace_proto = Proto("erlang_trace", "Erlang trace entry")
local types = {
[0] = "Binary term",
[1] = "Dropped trace messages",
}
local pf_entry_type = ProtoField.uint8("erlang_trace.entry_type", "Entry type",
base.DEC, types)
local pf_binary_size = ProtoField.uint32("erlang_trace.binary_size", "Binary size")
local pf_dropped_messages = ProtoField.uint32("erlang_trace.dropped_messages", "Number of dropped messages")
local pf_pid = ProtoField.string("erlang_trace.pid", "Process id")
local pf_event_type = ProtoField.string("erlang_trace.event_type", "Event type")
local pf_module = ProtoField.string("erlang_trace.module", "Module")
local pf_function = ProtoField.string("erlang_trace.function", "Function name")
local pf_arity = ProtoField.uint8("erlang_trace.arity", "Arity")
erlang_trace_proto.fields =
{ pf_entry_type, pf_dropped_messages, pf_binary_size,
pf_pid, pf_event_type, pf_module, pf_function, pf_arity }
local ef_unexpected = ProtoExpert.new("erlang_trace.unexpected", "Unexpected data",
expert.group.UNDECODED, expert.severity.WARN)
erlang_trace_proto.experts =
{ ef_unexpected }
local term_dissector = Dissector.get("erlterm")
local function handle_mfa(mfa, pktinfo, tree)
tree:add(pf_module, mfa[1])
tree:add(pf_function, mfa[2])
local arity
if type(mfa[3]) == "number" then
arity = mfa[3]
elseif type(mfa[3]) == "table" then
-- it's an argument list
arity = #mfa[3]
else
-- it's something we failed to decode
arity = "?"
end
if type(arity) == "number" then tree:add(pf_arity, arity) end
pktinfo.cols.info:append(" " .. mfa[1] .. ":" .. mfa[2] .. "/" .. arity)
end
local function decode_trace_data(value, pktinfo, tree)
if type(value) ~= "table" then
tree:add_proto_expert_info(ef_unexpected, "Not a tuple")
return
elseif value[1] ~= "trace_ts" then
tree:add_proto_expert_info(ef_unexpected, "Expected 'trace_ts', got '" .. value[1] .. "'")
return
end
tree:add(pf_pid, value[2])
local entry_type = value[3]
pktinfo.cols.info:set(entry_type)
tree:add(pf_event_type, entry_type)
if entry_type == "call" or entry_type == "return_to" then
-- Just one more element, M:F/A
handle_mfa(value[4], pktinfo, tree)
elseif entry_type == "return_from" then
-- M:F/A and return value
handle_mfa(value[4], pktinfo, tree)
-- TODO: how to deal with return value?
end
end
function erlang_trace_proto.dissector(tvbuf, pktinfo, root)
pktinfo.cols.protocol:set("Erlang trace")
local tree = root:add(erlang_trace_proto, tvbuf:range(0))
local t = tvbuf:range(0,1):uint()
tree:add(pf_entry_type, tvbuf:range(0, 1))
if t == 0 then
tree:add(pf_binary_size, tvbuf:range(1, 4))
--data_dissector(tvbuf:range(5):tvb(), pktinfo, root)
--term_dissector(tvbuf:range(5):tvb(), pktinfo, root)
--
-- Call the Erlang term dissector function directly instead of
-- going through the Wireshark dissector machinery, in order to
-- get access to the multiple return values.
local _, _, value = erlang_term_dissector(tvbuf:range(5):tvb(), pktinfo, root)
if value then
decode_trace_data(value, pktinfo, tree)
else
tree:add_proto_expert_info(ef_unexpected, "No value?")
end
elseif t == 1 then
tree:add(pf_dropped_messages, tvbuf:range(1, 4))
end
end
local function heur_dissect_erlang_trace(tvbuf, pktinfo, root)
local len = tvbuf:len()
local t = tvbuf:range(0, 1):uint()
local size = tvbuf:range(1, 4):uint()
if t == 0 and len ~= (size + 5) then
return false
elseif t == 1 and len ~= 5 then
return false
end
erlang_trace_proto.dissector(tvbuf, pktinfo, root)
return true
end
erlang_trace_proto:register_heuristic("wtap_file", heur_dissect_erlang_trace)
-- File handler
local erlang_trace_fh = FileHandler.new("Erlang trace file", "erlang trace",
"Erlang trace file as generated by trace_file_drv",
"r")
function erlang_trace_fh.read_open(file, cinfo)
-- There is no file header. Check that the file starts with a
-- binary block. That is, first a 0 byte...
if file:read(1) ~= "\0" then
return false
end
-- ...followed by a four byte length...
file:read(4)
-- ...followed by a term in external term format, i.e. starting with 131.
if file:read(1) ~= "\131" then
return false
end
-- XXX: what's a good value to put here?
cinfo.encap = wtap_encaps.MIME
cinfo.time_precision = wtap_tsprecs.USEC
file:seek("set", 0)
return true
end
local timestamp_regexp = GRegex.new(
-- We're parsing a binary, so "newlines" are not newlines. Let "."
-- match them as well: use the ?s flag.
"(?s)" ..
-- Five byte header, starting with 0. Ignore the length.
"^\\0...." ..
-- Erlang binary term, starting with 131 / hex 83
"\\x83" ..
-- It's a small tuple, type byte 104 / hex 68. Ignore the length.
"\\x68." ..
-- It's an ATOM_EXT, with a length of 8, stored as two bytes.
-- TODO: other atom formats?
"\\x64\\x00\\x08" ..
-- The atom name is "trace_ts" (8 bytes)
"trace_ts" ..
-- Skip to the end
".*" ..
-- The last element in the tuple is a small tuple with three elements.
"\\x68\\x03" ..
-- The three elements are either INTEGER_EXT or SMALL_INTEGER_EXT.
"(\\x62....|\\x61.)" ..
"(\\x62....|\\x61.)" ..
"(\\x62....|\\x61.)" ..
-- And that's the end.
"$")
local function erlang_trace_fh_read(file, cinfo, finfo)
local pos = file:seek("cur")
local op = file:read(1)
if not op then return false end
local size_s = file:read(4)
if not size_s then return false end
local size = Struct.unpack(">I4", size_s)
-- Seek back to beginning of frame, as we want to include the
-- entire thing in the frame data.
file:seek("set", pos)
local len
if op == "\0" then
-- It's a binary.
len = size + 5
elseif op == "\1" then
-- Dropped messages. The "size" is the number of messages
-- dropped. Shouldn't be written by the trace file driver, but
-- let's include it for completeness...
len = 5
end
finfo.original_length = len
finfo.captured_length = len
local data = file:read(len)
finfo.data = data
-- If the trace message starts with the trace_ts atom, then the
-- last element is a tuple of three integers. Probably need to be
-- flexible enough to accept either 8-bit or 32-bit integers.
--
-- That is if we use 'timestamp' as the trace option. But what
-- about 'strict_monotonic_timestamp' or
-- 'monotonic_timestamp'.... are those using integers?
local tsb1, tsb2, tsb3 = timestamp_regexp:match(data)
if tsb1 and tsb2 and tsb3 then
local megaseconds = decode_integer(tsb1)
local seconds = decode_integer(tsb2)
local microseconds = decode_integer(tsb3)
-- NSTime expects seconds and nanoseconds
finfo.time = NSTime(megaseconds * 1000000 + seconds, microseconds * 1000)
finfo.flags = wtap_presence_flags.TS
else
debug("No timestamp match")
end
return pos
end
erlang_trace_fh.read = erlang_trace_fh_read
decode_integer = function(bin)
if string.byte(bin, 1) == 97 then
-- Unsigned 8-bit integer
return Struct.unpack("I1", bin, 2)
elseif string.byte(bin, 1) == 98 then
-- Signed 32-bit integer, big-endian
return Struct.unpack(">i4", bin, 2)
else
error("unknown type " .. string.byte(bin, 1))
end
end
function erlang_trace_fh.seek_read(file, cinfo, finfo, offset)
file:seek("set", offset)
return erlang_trace_fh_read(file, cinfo, finfo) ~= false
end
erlang_trace_fh.extensions = "trace"
register_filehandler(erlang_trace_fh)