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