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