More licenses converted to SPDX.
[metze/wireshark/wip.git] / tools / pkt-from-core.py
1 #!/usr/bin/env python
2 """
3 Retrieve a packet from a wireshark/tshark core file
4 and save it in a packet-capture file.
5 """
6
7 # Copyright (C) 2013 by Gilbert Ramirez <gram@alumni.rice.edu>
8 #
9 # SPDX-License-Identifier: GPL-2.0-or-later
10
11 import getopt
12 import os
13 import re
14 import sys
15 import tempfile
16
17 exec_file = None
18 core_file = None
19 output_file = None
20
21 verbose = 0
22 debug = 0
23
24 class BackTrace:
25     re_frame = re.compile(r"^#(?P<num>\d+) ")
26     re_func1 = re.compile(r"^#\d+\s+(?P<func>\w+) \(")
27     re_func2 = re.compile(r"^#\d+\s+0x[A-Fa-f\d]+ in (?P<func>\w+) \(")
28
29     def __init__(self, lines):
30
31         # In order; each item is the function name.
32         self.frames = []
33         found_non_bt_frame = 0
34         frame_will_be = 0
35
36         for line in lines:
37             m = self.re_frame.search(line)
38             if m:
39                 # Skip the first frame that gdb shows,
40                 # which is not part of the backtrace.
41                 if not found_non_bt_frame:
42                     found_non_bt_frame = 1
43                     continue
44
45                 # Get the frame number and make sure it's
46                 # what we expect it should be.
47                 frame_num = int(m.group("num"))
48                 if frame_num != frame_will_be:
49                     sys.exit("Found frame %d instead of %d" % \
50                             (frame_num, frame_will_be))
51
52                 # Find the function name. XXX - need to handle '???'
53                 n = self.re_func1.search(line)
54                 if not n:
55                     n = self.re_func2.search(line)
56
57                 if n:
58                     func = n.group("func")
59                 else:
60                     sys.exit("Function name not found in %s" % (line,))
61
62                 # Save the info
63                 self.frames.append(func)
64                 frame_will_be += 1
65
66     def Frames(self):
67         return self.frames
68
69
70     def HasFunction(self, func):
71         return func in self.frames
72
73     def Frame(self, func):
74         return self.frames.index(func)
75
76
77 # Some values from wiretap; wiretap should be a shared
78 # libray and a Python module should be created for it so
79 # this program could just write a libpcap file directly.
80 WTAP_ENCAP_PER_PACKET                 = -1
81 WTAP_ENCAP_UNKNOWN                    = 0
82 WTAP_ENCAP_ETHERNET                   = 1
83 WTAP_ENCAP_TOKEN_RING                 = 2
84 WTAP_ENCAP_SLIP                       = 3
85 WTAP_ENCAP_PPP                        = 4
86 WTAP_ENCAP_FDDI                       = 5
87 WTAP_ENCAP_FDDI_BITSWAPPED            = 6
88 WTAP_ENCAP_RAW_IP                     = 7
89 WTAP_ENCAP_ARCNET                     = 8
90 WTAP_ENCAP_ATM_RFC1483                = 9
91 WTAP_ENCAP_LINUX_ATM_CLIP             = 10
92 WTAP_ENCAP_LAPB                       = 11
93 WTAP_ENCAP_ATM_SNIFFER                = 12
94 WTAP_ENCAP_NULL                       = 13
95 WTAP_ENCAP_ASCEND                     = 14
96 WTAP_ENCAP_LAPD                       = 15
97 WTAP_ENCAP_V120                       = 16
98 WTAP_ENCAP_PPP_WITH_PHDR              = 17
99 WTAP_ENCAP_IEEE_802_11                = 18
100 WTAP_ENCAP_SLL                        = 19
101 WTAP_ENCAP_FRELAY                     = 20
102 WTAP_ENCAP_CHDLC                      = 21
103 WTAP_ENCAP_CISCO_IOS                  = 22
104 WTAP_ENCAP_LOCALTALK                  = 23
105 WTAP_ENCAP_PRISM_HEADER               = 24
106 WTAP_ENCAP_PFLOG                      = 25
107 WTAP_ENCAP_AIROPEEK                   = 26
108 WTAP_ENCAP_HHDLC                      = 27
109 # last WTAP_ENCAP_ value + 1
110 WTAP_NUM_ENCAP_TYPES                  = 28
111
112 wtap_to_pcap_map = {
113         WTAP_ENCAP_NULL                 : 0,
114         WTAP_ENCAP_ETHERNET             : 1,
115         WTAP_ENCAP_TOKEN_RING           : 6,
116         WTAP_ENCAP_ARCNET               : 7,
117         WTAP_ENCAP_SLIP                 : 8,
118         WTAP_ENCAP_PPP                  : 9,
119         WTAP_ENCAP_FDDI_BITSWAPPED      : 10,
120         WTAP_ENCAP_FDDI                 : 10,
121         WTAP_ENCAP_ATM_RFC1483          : 11,
122         WTAP_ENCAP_RAW_IP               : 12,
123         WTAP_ENCAP_LINUX_ATM_CLIP       : 16, # or 18, or 19...
124         WTAP_ENCAP_CHDLC                : 104,
125         WTAP_ENCAP_IEEE_802_11          : 105,
126         WTAP_ENCAP_SLL                  : 113,
127         WTAP_ENCAP_LOCALTALK            : 114,
128         WTAP_ENCAP_PFLOG                : 117,
129         WTAP_ENCAP_CISCO_IOS            : 118,
130         WTAP_ENCAP_PRISM_HEADER         : 119,
131         WTAP_ENCAP_HHDLC                : 121,
132 }
133
134
135 wtap_name = {
136         WTAP_ENCAP_UNKNOWN                    : "Unknown",
137         WTAP_ENCAP_ETHERNET                   : "Ethernet",
138         WTAP_ENCAP_TOKEN_RING                 : "Token-Ring",
139         WTAP_ENCAP_SLIP                       : "SLIP",
140         WTAP_ENCAP_PPP                        : "PPP",
141         WTAP_ENCAP_FDDI                       : "FDDI",
142         WTAP_ENCAP_FDDI_BITSWAPPED            : "FDDI (Bitswapped)",
143         WTAP_ENCAP_RAW_IP                     : "Raw IP",
144         WTAP_ENCAP_ARCNET                     : "ARCNET",
145         WTAP_ENCAP_ATM_RFC1483                : "ATM RFC1483",
146         WTAP_ENCAP_LINUX_ATM_CLIP             : "Linux ATM CLIP",
147         WTAP_ENCAP_LAPB                       : "LAPB",
148         WTAP_ENCAP_ATM_SNIFFER                : "ATM Sniffer",
149         WTAP_ENCAP_NULL                       : "Null",
150         WTAP_ENCAP_ASCEND                     : "Ascend",
151         WTAP_ENCAP_LAPD                       : "LAPD",
152         WTAP_ENCAP_V120                       : "V.120",
153         WTAP_ENCAP_PPP_WITH_PHDR              : "PPP (with PHDR)",
154         WTAP_ENCAP_IEEE_802_11                : "IEEE 802.11",
155         WTAP_ENCAP_SLL                        : "SLL",
156         WTAP_ENCAP_FRELAY                     : "Frame Relay",
157         WTAP_ENCAP_CHDLC                      : "Cisco HDLC",
158         WTAP_ENCAP_CISCO_IOS                  : "Cisco IOS",
159         WTAP_ENCAP_LOCALTALK                  : "LocalTalk",
160         WTAP_ENCAP_PRISM_HEADER               : "Prism Header",
161         WTAP_ENCAP_PFLOG                      : "PFLog",
162         WTAP_ENCAP_AIROPEEK                   : "AiroPeek",
163         WTAP_ENCAP_HHDLC                      : "HHDLC",
164 }
165
166 def wtap_to_pcap(wtap):
167     if not wtap_to_pcap_map.has_key(wtap):
168         sys.exit("Don't know how to convert wiretap encoding %d to libpcap." % \
169                 (wtap))
170
171     return wtap_to_pcap_map[wtap]
172
173
174 def run_gdb(*commands):
175     if len(commands) == 0:
176         return []
177
178     # Create a temporary file
179     fname = tempfile.mktemp()
180     try:
181         fh = open(fname, "w")
182     except IOError, err:
183         sys.exit("Cannot open %s for writing: %s" % (fname, err))
184
185     # Put the commands in it
186     for cmd in commands:
187         fh.write(cmd)
188         fh.write("\n")
189
190     fh.write("quit\n")
191     try:
192         fh.close()
193     except IOError, err:
194         try:
195             os.unlink(fname)
196         except:
197             pass
198         sys.exit("Cannot close %s: %s" % (fname, err))
199
200
201     # Run gdb
202     cmd = "gdb --nw --quiet --command=%s %s %s" % (fname, exec_file, core_file)
203     if verbose:
204         print "Invoking %s" % (cmd,)
205     try:
206         pipe = os.popen(cmd)
207     except OSError, err:
208         try:
209             os.unlink(fname)
210         except:
211             pass
212         sys.exit("Cannot run gdb: %s" % (err,))
213
214     # Get gdb's output
215     result = pipe.readlines()
216     error = pipe.close()
217     if error != None:
218         try:
219             os.unlink(fname)
220         except:
221             pass
222         sys.exit("gdb returned an exit value of %s" % (error,))
223
224
225     # Remove the temp file and return the results
226     try:
227         os.unlink(fname)
228     except:
229         pass
230     return result
231
232 def get_value_from_frame(frame_num, variable, fmt=""):
233     cmds = []
234     if frame_num > 0:
235         cmds.append("up %d" % (frame_num,))
236
237     cmds.append("print %s %s" % (fmt, variable))
238     lines = apply(run_gdb, cmds)
239
240     LOOKING_FOR_START = 0
241     READING_VALUE = 1
242     state = LOOKING_FOR_START
243     result = ""
244     for line in lines:
245         if line[-1] == "\n":
246             line = line[0:-1]
247         if line[-1] == "\r":
248             line = line[0:-1]
249
250         if state == LOOKING_FOR_START:
251             if len(line) < 4:
252                 continue
253             else:
254                 if line[0:4] == "$1 =":
255                     result = line[4:]
256                     state = READING_VALUE
257
258         elif state == READING_VALUE:
259             result += line
260
261     return result
262
263 def get_int_from_frame(frame_num, variable):
264     text = get_value_from_frame(frame_num, variable)
265     try:
266         integer = int(text)
267     except ValueError:
268         sys.exit("Could not convert '%s' to integer." % (text,))
269     return integer
270
271
272 def get_byte_array_from_frame(frame_num, variable, length):
273     cmds = []
274     if frame_num > 0:
275         cmds.append("up %d" % (frame_num,))
276
277     cmds.append("print %s" % (variable,))
278     cmds.append("x/%dxb %s" % (length, variable))
279     lines = apply(run_gdb, cmds)
280     if debug:
281         print lines
282
283     bytes = []
284
285     LOOKING_FOR_START = 0
286     BYTES = 1
287     state = LOOKING_FOR_START
288
289     for line in lines:
290         if state == LOOKING_FOR_START:
291             if len(line) < 3:
292                 continue
293             elif line[0:3] == "$1 ":
294                 state = BYTES
295         elif state == BYTES:
296             line.rstrip()
297             fields = line.split('\t')
298             if fields[0][-1] != ":":
299                 print "Failed to parse byte array from gdb:"
300                 print line
301                 sys.exit(1)
302
303             for field in fields[1:]:
304                 val = int(field, 16)
305                 bytes.append(val)
306         else:
307             assert 0
308
309     return bytes
310
311 def make_cap_file(pkt_data, lnk_t):
312
313     pcap_lnk_t = wtap_to_pcap(lnk_t)
314
315     # Create a temporary file
316     fname = tempfile.mktemp()
317     try:
318         fh = open(fname, "w")
319     except IOError, err:
320         sys.exit("Cannot open %s for writing: %s" % (fname, err))
321
322     print "Packet Data:"
323
324     # Put the hex dump in it
325     offset = 0
326     BYTES_IN_ROW = 16
327     for byte in pkt_data:
328         if (offset % BYTES_IN_ROW) == 0:
329             print >> fh, "\n%08X  " % (offset,),
330             print "\n%08X  " % (offset,),
331
332         print >> fh, "%02X " % (byte,),
333         print "%02X " % (byte,),
334         offset += 1
335
336     print >> fh, "\n"
337     print "\n"
338
339     try:
340         fh.close()
341     except IOError, err:
342         try:
343             os.unlink(fname)
344         except:
345             pass
346         sys.exit("Cannot close %s: %s" % (fname, err))
347
348
349     # Run text2pcap
350     cmd = "text2pcap -q -l %s %s %s" % (pcap_lnk_t, fname, output_file)
351 #       print "Command is %s" % (cmd,)
352     try:
353         retval = os.system(cmd)
354     except OSError, err:
355         try:
356             os.unlink(fname)
357         except:
358             pass
359         sys.exit("Cannot run text2pcap: %s" % (err,))
360
361     # Remove the temp file
362     try:
363         os.unlink(fname)
364     except:
365         pass
366
367     if retval == 0:
368         print "%s created with %d bytes in packet, and %s encoding." % \
369                 (output_file, len(pkt_data), wtap_name[lnk_t])
370     else:
371         sys.exit("text2pcap did not run successfully.")
372
373
374
375
376 def try_frame(func_text, cap_len_text, lnk_t_text, data_text):
377
378     # Get the back trace
379     bt_text = run_gdb("bt")
380     bt = BackTrace(bt_text)
381     if not bt.HasFunction(func_text):
382         print "%s() not found in backtrace." % (func_text,)
383         return 0
384     else:
385         print "%s() found in backtrace." % (func_text,)
386
387     # Figure out where the call to epan_dissect_run is.
388     frame_num = bt.Frame(func_text)
389
390     # Get the capture length
391     cap_len = get_int_from_frame(frame_num, cap_len_text)
392
393     # Get the encoding type
394     lnk_t = get_int_from_frame(frame_num, lnk_t_text)
395
396     # Get the packet data
397     pkt_data = get_byte_array_from_frame(frame_num, data_text, cap_len)
398
399     if verbose:
400         print "Length=%d" % (cap_len,)
401         print "Encoding=%d" % (lnk_t,)
402         print "Data (%d bytes) = %s" % (len(pkt_data), pkt_data)
403     make_cap_file(pkt_data, lnk_t)
404     return 1
405
406 def run():
407     if try_frame("epan_dissect_run",
408             "fd->cap_len", "fd->lnk_t", "data"):
409         return
410     elif try_frame("add_packet_to_packet_list",
411             "fdata->cap_len", "fdata->lnk_t", "buf"):
412         return
413     else:
414         sys.exit("A packet cannot be pulled from this core.")
415
416
417 def usage():
418     print "pkt-from-core.py [-v] -w capture_file executable-file (core-file or process-id)"
419     print ""
420     print "\tGiven an executable file and a core file, this tool"
421     print "\tuses gdb to retrieve the packet that was being dissected"
422     print "\tat the time wireshark/tshark stopped running. The packet"
423     print "\tis saved in the capture_file specified by the -w option."
424     print ""
425     print "\t-v : verbose"
426     sys.exit(1)
427
428 def main():
429     global exec_file
430     global core_file
431     global output_file
432     global verbose
433     global debug
434
435     optstring = "dvw:"
436     try:
437         opts, args = getopt.getopt(sys.argv[1:], optstring)
438     except getopt.error:
439         usage()
440
441     for opt, arg in opts:
442         if opt == "-w":
443             output_file = arg
444         elif opt == "-v":
445             verbose = 1
446         elif opt == "-d":
447             debug = 1
448         else:
449             assert 0
450
451     if output_file == None:
452         usage()
453
454     if len(args) != 2:
455         usage()
456
457     exec_file = args[0]
458     core_file = args[1]
459
460     run()
461
462 if __name__ == '__main__':
463     main()
464
465 #
466 # Editor modelines  -  http://www.wireshark.org/tools/modelines.html
467 #
468 # Local variables:
469 # c-basic-offset: 4
470 # indent-tabs-mode: nil
471 # End:
472 #
473 # vi: set shiftwidth=4 expandtab:
474 # :indentSize=4:noTabs=true:
475 #