rpathsync / epdb
An Extend Python debugger, based on the python debugger pdb. Epdb adds readline support amongst other features to pdb.
| commit 40: | de3c89d2fecf |
| parent 39: | d4065b9aaf2c |
| branch: | default |
add telnetserver + telnet client
3 years ago
Changed (Δ13.6 KB):
raw changeset »
epdb/epdb.py (89 lines added, 14 lines removed)
epdb/telnetclient.py (83 lines added, 0 lines removed)
epdb/telnetserver.py (261 lines added, 0 lines removed)
1 |
#!/usr/bin/python2.4 |
|
1 |
2 |
# |
2 |
3 |
# Copyright (c) 2004-2005 rPath, Inc. |
3 |
4 |
# |
| … | … | @@ -24,12 +25,21 @@ except ImportError: |
24 |
25 |
hasReadline = False |
25 |
26 |
else: |
26 |
27 |
hasReadline = True |
28 |
import signal |
|
27 |
29 |
import socket |
28 |
30 |
import string |
29 |
31 |
import sys |
30 |
32 |
import tempfile |
31 |
33 |
import traceback |
32 |
34 |
|
35 |
try: |
|
36 |
sys.path.append('/home/dbc/rpl/hg/telnet') |
|
37 |
import telnetserver |
|
38 |
import telnetclient |
|
39 |
hasTelnet = True |
|
40 |
except ImportError: |
|
41 |
hasTelnet = False |
|
42 |
||
33 |
43 |
from pdb import _saferepr |
34 |
44 |
|
35 |
45 |
class Epdb(pdb.Pdb): |
| … | … | @@ -47,8 +57,8 @@ class Epdb(pdb.Pdb): |
47 |
57 |
# used to track the number of times a set_trace has been seen |
48 |
58 |
trace_counts = {'default' : [ True, 0 ]} |
49 |
59 |
|
50 |
||
51 |
60 |
def __init__(self): |
61 |
self._server = None |
|
52 |
62 |
self._exc_type = None |
53 |
63 |
self._exc_msg = None |
54 |
64 |
self._tb = None |
| … | … | @@ -99,6 +109,38 @@ class Epdb(pdb.Pdb): |
99 |
109 |
else: |
100 |
110 |
readline.clear_history() |
101 |
111 |
|
112 |
if hasTelnet: |
|
113 |
# telnet server support. |
|
114 |
# if enabled, you can serve a epdb session. |
|
115 |
def serve(self, port=8080): |
|
116 |
print 'Serving on port %s' % port |
|
117 |
self._server = telnetserver.InvertedTelnetServer(('', port)) |
|
118 |
self._server.handle_request() |
|
119 |
self.set_trace(skip=2) |
|
120 |
||
121 |
def serve_post_mortem(self, t, exc_type=None, exc_msg=None, port=8080): |
|
122 |
print 'Serving on port %s' % port |
|
123 |
self._server = telnetserver.InvertedTelnetServer(('', port)) |
|
124 |
self._server.handle_request() |
|
125 |
self.post_mortem(t, exc_type, exc_msg, port) |
|
126 |
||
127 |
def do_detach(self, arg): |
|
128 |
if self.server: |
|
129 |
print ('Leaving process in debug state - use "close" to' |
|
130 |
' stop debug session') |
|
131 |
self._server.close_request() |
|
132 |
self._server.handle_request() |
|
133 |
else: |
|
134 |
print "Not attached via telnet" |
|
135 |
||
136 |
def do_close(self, arg): |
|
137 |
if self.server: |
|
138 |
print 'Ending epdb session - use "detach" to stop serving' |
|
139 |
self._server.close_request() |
|
140 |
return True |
|
141 |
else: |
|
142 |
print "Not attached via telnet" |
|
143 |
||
102 |
144 |
def do_savestack(self, path): |
103 |
145 |
if 'stack' in self.__dict__: |
104 |
146 |
# when we're saving we always |
| … | … | @@ -304,8 +346,6 @@ class Epdb(pdb.Pdb): |
304 |
346 |
return sys.modules[origFileName].__file__ |
305 |
347 |
return None |
306 |
348 |
|
307 |
||
308 |
||
309 |
349 |
def default(self, line): |
310 |
350 |
if line[0] == '!': line = line[1:] |
311 |
351 |
if self.handle_directive(line): |
| … | … | @@ -327,7 +367,6 @@ class Epdb(pdb.Pdb): |
327 |
367 |
return pdb.Pdb.default(self, origLine) |
328 |
368 |
finally: |
329 |
369 |
self.read_history() |
330 |
||
331 |
370 |
|
332 |
371 |
def multiline(self, firstline=''): |
333 |
372 |
full_input = [] |
| … | … | @@ -669,17 +708,26 @@ class Epdb(pdb.Pdb): |
669 |
708 |
self.save_history(restoreOldHistory=True) |
670 |
709 |
self.restore_input_output() |
671 |
710 |
|
711 |
def post_mortem(self, t, exc_type, exc_msg): |
|
712 |
p._exc_type = exc_type |
|
713 |
p._exc_msg = exc_msg |
|
714 |
p._tb = t |
|
715 |
p.reset() |
|
716 |
while t.tb_next is not None: |
|
717 |
t = t.tb_next |
|
718 |
p.interaction(t.tb_frame, t) |
|
719 |
||
672 |
720 |
def switch_input_output(self): |
673 |
721 |
self.switch_stdout() |
674 |
722 |
self.switch_stdin() |
675 |
723 |
self.switch_pgid() |
676 |
724 |
|
677 |
725 |
def restore_input_output(self): |
678 |
if |
|
726 |
if self.__old_stdout is not None: |
|
679 |
727 |
sys.stdout.flush() |
680 |
728 |
# now we reset stdout to be the whatever it was before |
681 |
729 |
sys.stdout = self.__old_stdout |
682 |
if |
|
730 |
if self.__old_stdin is not None: |
|
683 |
731 |
sys.stdin = self.__old_stdin |
684 |
732 |
if self.__old_pgid is not None: |
685 |
733 |
os.setpgid(0, self.__old_pgid) |
| … | … | @@ -903,15 +951,19 @@ def set_trace(marker='default'): |
903 |
951 |
|
904 |
952 |
st = set_trace |
905 |
953 |
|
954 |
if hasTelnet: |
|
955 |
def serve(port=8080): |
|
956 |
Epdb().serve(port) |
|
957 |
||
958 |
def serve_post_mortem(t, exc_type=None, exc_msg=None, port=8080): |
|
959 |
Epdb().serve_post_mortem(t, exc_type, exc_msg, port) |
|
960 |
||
961 |
def connect(host='localhost', port=8080): |
|
962 |
t = telnetclient.TelnetClient(host, port) |
|
963 |
t.interact() |
|
964 |
||
906 |
965 |
def post_mortem(t, exc_type=None, exc_msg=None): |
907 |
p = Epdb() |
|
908 |
p._exc_type = exc_type |
|
909 |
p._exc_msg = exc_msg |
|
910 |
p._tb = t |
|
911 |
p.reset() |
|
912 |
while t.tb_next is not None: |
|
913 |
t = t.tb_next |
|
914 |
p.interaction(t.tb_frame, t) |
|
966 |
Epdb().post_mortem(t, exc_type=None, exc_msg=None) |
|
915 |
967 |
|
916 |
968 |
def matchFileOnDirPath(curpath, pathdir): |
917 |
969 |
"""Find match for a file by slicing away its directory elements |
| … | … | @@ -985,3 +1037,26 @@ def _removeQuoteSet(line, quote1, quote2 |
985 |
1037 |
secondPoint += firstPoint |
986 |
1038 |
line = line[:firstPoint] + line[(secondPoint+2*ln):] |
987 |
1039 |
|
1040 |
def main(argv): |
|
1041 |
if len(argv) == 1: |
|
1042 |
set_trace() |
|
1043 |
return |
|
1044 |
command = argv[1] |
|
1045 |
if command == 'connect': |
|
1046 |
if len(argv) > 2: |
|
1047 |
host = argv[2] |
|
1048 |
else: |
|
1049 |
host = 'localhost' |
|
1050 |
if len(argv) > 3: |
|
1051 |
port = int(argv[3]) |
|
1052 |
else: |
|
1053 |
port = 8080 |
|
1054 |
connect(host=host, port=port) |
|
1055 |
else: |
|
1056 |
print "usage: connect [host] [port]" |
|
1057 |
return 1 |
|
1058 |
return 0 |
|
1059 |
||
1060 |
if __name__ == '__main__': |
|
1061 |
sys.exit(main(sys.argv)) |
|
1062 |
Up to file-list epdb/telnetclient.py:
1 |
#!/usr/bin/python |
|
2 |
import errno |
|
3 |
import fcntl |
|
4 |
import os |
|
5 |
import select |
|
6 |
import signal |
|
7 |
import struct |
|
8 |
import sys |
|
9 |
import telnetlib |
|
10 |
import termios |
|
11 |
||
12 |
from telnetlib import IAC, IP, SB, SE, NAWS |
|
13 |
||
14 |
def getTerminalSize(): |
|
15 |
s = struct.pack('HHHH', 0, 0, 0, 0) |
|
16 |
result = fcntl.ioctl(sys.stdin.fileno(), termios.TIOCGWINSZ, s) |
|
17 |
rows, cols = struct.unpack('HHHH', result)[0:2] |
|
18 |
return rows, cols |
|
19 |
||
20 |
class TelnetClient(telnetlib.Telnet): |
|
21 |
def __init__(self, *args, **kw): |
|
22 |
telnetlib.Telnet.__init__(self, *args, **kw) |
|
23 |
signal.signal(signal.SIGINT, self.ctrl_c) |
|
24 |
signal.signal(signal.SIGWINCH, self.sigwinch) |
|
25 |
self.oldTerm = None |
|
26 |
self.oldFlags = None |
|
27 |
||
28 |
def set_raw_mode(self): |
|
29 |
fd = sys.stdin.fileno() |
|
30 |
self.oldTerm = termios.tcgetattr(fd) |
|
31 |
newattr = termios.tcgetattr(fd) |
|
32 |
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO |
|
33 |
termios.tcsetattr(fd, termios.TCSANOW, newattr) |
|
34 |
self.oldFlags = fcntl.fcntl(fd, fcntl.F_GETFL) |
|
35 |
fcntl.fcntl(fd, fcntl.F_SETFL, self.oldFlags | os.O_NONBLOCK) |
|
36 |
||
37 |
def restore_terminal(self): |
|
38 |
fd = sys.stdin.fileno() |
|
39 |
if self.oldTerm: |
|
40 |
termios.tcsetattr(fd, termios.TCSAFLUSH, self.oldTerm) |
|
41 |
if self.oldFlags: |
|
42 |
fcntl.fcntl(fd, fcntl.F_SETFL, self.oldFlags) |
|
43 |
||
44 |
def ctrl_c(self, int, tb): |
|
45 |
self.sock.sendall(IAC + IP) |
|
46 |
||
47 |
def sigwinch(self, int, tb): |
|
48 |
self.updateTerminalSize() |
|
49 |
||
50 |
def updateTerminalSize(self): |
|
51 |
rows, cols = getTerminalSize() |
|
52 |
self.sock.sendall(IAC + SB + NAWS + chr(cols) + chr(rows) + IAC + SE) |
|
53 |
||
54 |
def interact(self): |
|
55 |
self.set_raw_mode() |
|
56 |
self.updateTerminalSize() |
|
57 |
try: |
|
58 |
while 1: |
|
59 |
try: |
|
60 |
rfd, wfd, xfd = select.select([self, sys.stdin], [], []) |
|
61 |
except select.error, err: |
|
62 |
if err.args[0] != errno.EINTR: # ignore interrupted select |
|
63 |
raise |
|
64 |
if self in rfd: |
|
65 |
try: |
|
66 |
text = self.read_eager() |
|
67 |
except EOFError: |
|
68 |
print '*** Connection closed by remote host ***' |
|
69 |
break |
|
70 |
if text: |
|
71 |
sys.stdout.write(text) |
|
72 |
sys.stdout.flush() |
|
73 |
if sys.stdin in rfd: |
|
74 |
line = sys.stdin.read(4096) |
|
75 |
if not line: |
|
76 |
break |
|
77 |
self.write(line) |
|
78 |
finally: |
|
79 |
self.restore_terminal() |
|
80 |
||
81 |
if __name__ == '__main__': |
|
82 |
t = TelnetClient('localhost', 8000) |
|
83 |
t.interact() |
Up to file-list epdb/telnetserver.py:
1 |
#!/usr/bin/python |
|
2 |
# Copyright (c) 2005-2006 rPath, Inc. |
|
3 |
# |
|
4 |
# This program is distributed under the terms of the Common Public License, |
|
5 |
# version 1.0. A copy of this license should have been distributed with this |
|
6 |
# source file in a file called LICENSE. If it is not present, the license |
|
7 |
# is always available at http://www.opensource.org/licenses/cpl.php. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, but |
|
10 |
# without any warranty; without even the implied warranty of merchantability |
|
11 |
# or fitness for a particular purpose. See the Common Public License for |
|
12 |
# full details. |
|
13 |
# |
|
14 |
""" |
|
15 |
Telnet Server implementation. |
|
16 |
||
17 |
Based on telnetlib telnet client - reads in and parses telnet protocol |
|
18 |
from the socket, understands window change requests and interrupt requests. |
|
19 |
(IP and NAWS). |
|
20 |
||
21 |
This server does _NOT_ do LINEMODE, instead it is character based. This means |
|
22 |
to talk to this server using the standard telnet client, you'll need to first |
|
23 |
type "CTRL-] mode char\n" |
|
24 |
""" |
|
25 |
from SocketServer import TCPServer, BaseRequestHandler |
|
26 |
import fcntl |
|
27 |
import os |
|
28 |
import pty |
|
29 |
import select |
|
30 |
import signal |
|
31 |
import socket |
|
32 |
import struct |
|
33 |
import sys |
|
34 |
import telnetlib |
|
35 |
import termios |
|
36 |
import tty |
|
37 |
from telnetlib import IAC, IP, SB, SE, DO, DONT, WILL, WONT, TM, LINEMODE, NAWS |
|
38 |
||
39 |
class TelnetServerProtocolHandler(telnetlib.Telnet): |
|
40 |
""" |
|
41 |
Code that actually understands telnet protocol. |
|
42 |
Accepts telnet-coded input from the socket and passes on that |
|
43 |
information to local, which should be the master for a pty controlled |
|
44 |
process. |
|
45 |
""" |
|
46 |
def __init__(self, socket, local): |
|
47 |
telnetlib.Telnet.__init__(self) |
|
48 |
self.sock = socket |
|
49 |
self.remote = self.sock.fileno() |
|
50 |
self.local = local |
|
51 |
self.set_option_negotiation_callback(self.process_IAC) |
|
52 |
||
53 |
def process_IAC(self, sock, cmd, option): |
|
54 |
""" |
|
55 |
Read in and parse IAC commands as passed by telnetlib. |
|
56 |
||
57 |
SB/SE commands are stored in sbdataq, and passed in w/ a command |
|
58 |
of SE. |
|
59 |
""" |
|
60 |
if cmd == DO: |
|
61 |
if option == TM: # timing mark - send WILL into outgoing stream |
|
62 |
os.write(self.remote, IAC + WILL + TM) |
|
63 |
else: |
|
64 |
pass |
|
65 |
elif cmd == IP: |
|
66 |
# interrupt process |
|
67 |
os.write(self.local, chr(ord('C') & 0x1F)) |
|
68 |
elif cmd == SB: |
|
69 |
pass |
|
70 |
elif cmd == SE: |
|
71 |
option = self.sbdataq[0] |
|
72 |
if option == NAWS: # negotiate window size. |
|
73 |
cols = ord(self.sbdataq[1]) |
|
74 |
rows = ord(self.sbdataq[2]) |
|
75 |
s = struct.pack('HHHH', rows, cols, 0, 0) |
|
76 |
fcntl.ioctl(self.local, termios.TIOCSWINSZ, s) |
|
77 |
elif cmd == DONT: |
|
78 |
pass |
|
79 |
else: |
|
80 |
pass |
|
81 |
||
82 |
def handle(self): |
|
83 |
""" |
|
84 |
Performs endless processing of socket input/output, passing |
|
85 |
cooked information onto the local process. |
|
86 |
""" |
|
87 |
while True: |
|
88 |
toRead = select.select([self.local, self.remote], [], [], 0.1)[0] |
|
89 |
if self.local in toRead: |
|
90 |
data = os.read(self.local, 4096) |
|
91 |
self.sock.sendall(data) |
|
92 |
continue |
|
93 |
if self.remote in toRead or self.rawq: |
|
94 |
buf = self.read_eager() |
|
95 |
os.write(self.local, buf) |
|
96 |
continue |
|
97 |
||
98 |
class TelnetRequestHandler(BaseRequestHandler): |
|
99 |
""" |
|
100 |
Request handler that serves up a shell for users who connect. |
|
101 |
Derive from this class to change the execute() method to change how |
|
102 |
what command the request serves to the client. |
|
103 |
""" |
|
104 |
command = '/bin/sh' |
|
105 |
args = ['/bin/sh'] |
|
106 |
||
107 |
def setup(self): |
|
108 |
pass |
|
109 |
||
110 |
def handle(self): |
|
111 |
""" |
|
112 |
Creates a child process that is fully controlled by this |
|
113 |
request handler, and serves data to and from it via the |
|
114 |
protocol handler. |
|
115 |
""" |
|
116 |
pid, fd = pty.fork() |
|
117 |
if pid: |
|
118 |
protocol = TelnetServerProtocolHandler(self.request, fd) |
|
119 |
protocol.handle() |
|
120 |
else: |
|
121 |
self.execute() |
|
122 |
||
123 |
def execute(self): |
|
124 |
try: |
|
125 |
os.execv(self.command, self.args) |
|
126 |
finally: |
|
127 |
os._exit(1) |
|
128 |
||
129 |
def finish(self): |
|
130 |
pass |
|
131 |
||
132 |
||
133 |
class TelnetServer(TCPServer): |
|
134 |
||
135 |
allow_reuse_address = True |
|
136 |
||
137 |
def __init__(self, server_address=None, |
|
138 |
requestHandlerClass=TelnetRequestHandler): |
|
139 |
if not server_address: |
|
140 |
server_address = ('', 23) |
|
141 |
TCPServer.__init__(self, server_address, requestHandlerClass) |
|
142 |
||
143 |
class TelnetServerForCommand(TelnetServer): |
|
144 |
def __init__(self, server_address=None, |
|
145 |
requestHandlerClass=TelnetRequestHandler, |
|
146 |
command=['/bin/sh']): |
|
147 |
class RequestHandler(requestHandlerClass): |
|
148 |
pass |
|
149 |
RequestHandler.command = command[0] |
|
150 |
RequestHandler.args = command |
|
151 |
TelnetServer.__init__(self, server_address, RequestHandler) |
|
152 |
||
153 |
class InvertedTelnetRequestHandler(TelnetRequestHandler): |
|
154 |
def handle(self): |
|
155 |
masterFd, slaveFd = pty.openpty() |
|
156 |
||
157 |
signal.signal(signal.SIGTTOU, signal.SIG_IGN) |
|
158 |
pid = os.fork() |
|
159 |
if pid: |
|
160 |
os.close(masterFd) |
|
161 |
raise SocketConnected(slaveFd, pid) |
|
162 |
# make parent process the pty slave - the opposite of |
|
163 |
# pty.fork(). In this setup, the parent process continues |
|
164 |
# to act normally, while the child process performs the |
|
165 |
# logging. This makes it simple to kill the logging process |
|
166 |
# when we are done with it and restore the parent process to |
|
167 |
# normal, unlogged operation. |
|
168 |
else: |
|
169 |
os.close(slaveFd) |
|
170 |
try: |
|
171 |
protocol = TelnetServerProtocolHandler(self.request, masterFd) |
|
172 |
protocol.handle() |
|
173 |
finally: |
|
174 |
os._exit(1) |
|
175 |
||
176 |
class InvertedTelnetServer(TelnetServer): |
|
177 |
""" |
|
178 |
Creates a telnet server that controls the stdin and stdout |
|
179 |
of the current process, instead of serving a subprocess. |
|
180 |
||
181 |
The telnet server can be closed at any time, and when it is |
|
182 |
input and output for the current process will be restored. |
|
183 |
""" |
|
184 |
def __init__(self, server_address=None, |
|
185 |
requestHandlerClass=InvertedTelnetRequestHandler): |
|
186 |
TelnetServer.__init__(self, server_address, requestHandlerClass) |
|
187 |
self.closed = True |
|
188 |
self.oldStdin = self.oldStdout = self.oldStderr = None |
|
189 |
self.oldTermios = None |
|
190 |
||
191 |
def handle_request(self): |
|
192 |
""" |
|
193 |
Handle one request - serve current process to one connection. |
|
194 |
||
195 |
Use close_request() to disconnect this process. |
|
196 |
""" |
|
197 |
try: |
|
198 |
request, client_address = self.get_request() |
|
199 |
except socket.error: |
|
200 |
return |
|
201 |
if self.verify_request(request, client_address): |
|
202 |
try: |
|
203 |
self.process_request(request, client_address) |
|
204 |
except SocketConnected, err: |
|
205 |
self._serve_process(err.slaveFd, err.serverPid) |
|
206 |
return |
|
207 |
except: |
|
208 |
self.handle_error(request, client_address) |
|
209 |
self.close_request(request) |
|
210 |
||
211 |
def _serve_process(self, slaveFd, serverPid): |
|
212 |
""" |
|
213 |
Serves a process by connecting its outputs/inputs to the pty |
|
214 |
slaveFd. serverPid is the process controlling the master fd |
|
215 |
that passes that output over the socket. |
|
216 |
""" |
|
217 |
self.serverPid = serverPid |
|
218 |
if sys.stdin.isatty(): |
|
219 |
self.oldTermios = termios.tcgetattr(sys.stdin.fileno()) |
|
220 |
else: |
|
221 |
self.oldTermios = None |
|
222 |
self.oldStderr = os.dup(sys.stderr.fileno()) |
|
223 |
self.oldStdout = os.dup(sys.stdout.fileno()) |
|
224 |
self.oldStdin = os.dup(sys.stdin.fileno()) |
|
225 |
os.dup2(slaveFd, 0) |
|
226 |
os.dup2(slaveFd, 1) |
|
227 |
os.dup2(slaveFd, 2) |
|
228 |
os.close(slaveFd) |
|
229 |
self.closed = False |
|
230 |
||
231 |
def close_request(self): |
|
232 |
if self.closed: |
|
233 |
pass |
|
234 |
self.closed = True |
|
235 |
# restore old terminal settings before quitting |
|
236 |
os.dup2(self.oldStdin, 0) |
|
237 |
os.dup2(self.oldStdout, 1) |
|
238 |
os.dup2(self.oldStderr, 2) |
|
239 |
if self.oldTermios is not None: |
|
240 |
termios.tcsetattr(0, termios.TCSADRAIN, self.oldTermios) |
|
241 |
os.close(self.oldStdin) |
|
242 |
os.close(self.oldStdout) |
|
243 |
os.close(self.oldStderr) |
|
244 |
os.waitpid(self.serverPid, 0) |
|
245 |
||
246 |
class SocketConnected(Exception): |
|
247 |
""" |
|
248 |
Control-Flow Exception raised when we have successfully connected |
|
249 |
a socket. |
|
250 |
||
251 |
Used for IntertedTelnetServer |
|
252 |
""" |
|
253 |
def __init__(self, slaveFd, serverPid): |
|
254 |
self.slaveFd = slaveFd |
|
255 |
self.serverPid = serverPid |
|
256 |
||
257 |
||
258 |
if __name__ == '__main__': |
|
259 |
print 'serving on 8081....' |
|
260 |
t = TelnetServer(('', 8081)) |
|
261 |
t.serve_forever() |
