|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: iso-8859-1 -*- |
| 3 | +# Copyright (C) 2008-2010, 2013-2014, 2016-2017, 2021 |
| 4 | +# Rocky Bernstein <rocky@gnu.org> |
| 5 | +# |
| 6 | +# This program is free software: you can redistribute it and/or modify |
| 7 | +# it under the terms of the GNU General Public License as published by |
| 8 | +# the Free Software Foundation, either version 3 of the License, or |
| 9 | +# (at your option) any later version. |
| 10 | +# |
| 11 | +# This program is distributed in the hope that it will be useful, |
| 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | +# GNU General Public License for more details. |
| 15 | +# |
| 16 | +# You should have received a copy of the GNU General Public License |
| 17 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | +"""The command-line interface to the debugger. |
| 19 | +""" |
| 20 | +import pyficache, os, sys, tempfile |
| 21 | +import os.path as osp |
| 22 | + |
| 23 | +import trepan.client as Mclient |
| 24 | +import trepan.clifns as Mclifns |
| 25 | +import trepan.debugger as Mdebugger |
| 26 | +import trepan.exception as Mexcept |
| 27 | +import trepan.options as Moptions |
| 28 | +import trepan.interfaces.server as Mserver |
| 29 | +import trepan.lib.file as Mfile |
| 30 | +import trepan.misc as Mmisc |
| 31 | + |
| 32 | +# The name of the debugger we are currently going by. |
| 33 | +__title__ = "trepan2" |
| 34 | + |
| 35 | +from trepan.version import __version__ |
| 36 | + |
| 37 | + |
| 38 | +def main(dbg=None, sys_argv=list(sys.argv)): |
| 39 | + """Routine which gets run if we were invoked directly""" |
| 40 | + |
| 41 | + # Save the original just for use in the restart that works via exec. |
| 42 | + orig_sys_argv = list(sys_argv) |
| 43 | + opts, dbg_opts, sys_argv = Moptions.process_options( |
| 44 | + __title__, __version__, sys_argv |
| 45 | + ) |
| 46 | + if opts.server: |
| 47 | + connection_opts = {"IO": "TCP", "PORT": opts.port} |
| 48 | + intf = Mserver.ServerInterface(connection_opts=connection_opts) |
| 49 | + dbg_opts["interface"] = intf |
| 50 | + if "FIFO" == intf.server_type: |
| 51 | + print("Starting FIFO server for process %s." % os.getpid()) |
| 52 | + elif "TCP" == intf.server_type: |
| 53 | + print("Starting TCP server listening on port %s." % intf.inout.PORT) |
| 54 | + pass |
| 55 | + elif opts.client: |
| 56 | + Mclient.main(opts, sys_argv) |
| 57 | + return |
| 58 | + |
| 59 | + dbg_opts["orig_sys_argv"] = orig_sys_argv |
| 60 | + |
| 61 | + if dbg is None: |
| 62 | + dbg = Mdebugger.Debugger(dbg_opts) |
| 63 | + dbg.core.add_ignore(main) |
| 64 | + pass |
| 65 | + Moptions._postprocess_options(dbg, opts) |
| 66 | + |
| 67 | + # process_options has munged sys.argv to remove any options that |
| 68 | + # options that belong to this debugger. The original options to |
| 69 | + # invoke the debugger and script are in global sys_argv |
| 70 | + |
| 71 | + if len(sys_argv) == 0: |
| 72 | + # No program given to debug. Set to go into a command loop |
| 73 | + # anyway |
| 74 | + mainpyfile = None |
| 75 | + else: |
| 76 | + mainpyfile = sys_argv[0] # Get script filename. |
| 77 | + if not osp.isfile(mainpyfile): |
| 78 | + mainpyfile = Mclifns.whence_file(mainpyfile) |
| 79 | + is_readable = Mfile.readable(mainpyfile) |
| 80 | + if is_readable is None: |
| 81 | + sys.stderr.write("%s: Python script file '%s' does not exist\n" |
| 82 | + % (__title__, mainpyfile)) |
| 83 | + sys.exit(1) |
| 84 | + elif not is_readable: |
| 85 | + sys.stderr.write("%s: Can't read Python script file '%s'\n" |
| 86 | + % (__title__, mainpyfile, )) |
| 87 | + sys.exit(1) |
| 88 | + return |
| 89 | + |
| 90 | + if Mfile.is_compiled_py(mainpyfile): |
| 91 | + try: |
| 92 | + from xdis import load_module |
| 93 | + from xdis_version import load_module, PYTHON_VERSION_TRIPLE, IS_PYPY, version_tuple_to_str |
| 94 | + |
| 95 | + ( |
| 96 | + python_version, |
| 97 | + timestamp, |
| 98 | + magic_int, |
| 99 | + co, |
| 100 | + is_pypy, |
| 101 | + source_size, |
| 102 | + ) = load_module(mainpyfile, code_objects=None, fast_load=True) |
| 103 | + assert is_pypy == IS_PYPY |
| 104 | + assert ( |
| 105 | + python_version == PYTHON_VERSION_TRIPLE |
| 106 | + ), "bytecode is for version %s but we are version %s" % ( |
| 107 | + python_version, |
| 108 | + version_tuple_to_str(), |
| 109 | + ) |
| 110 | + # We should we check version magic_int |
| 111 | + |
| 112 | + py_file = co.co_filename |
| 113 | + if osp.isabs(py_file): |
| 114 | + try_file = py_file |
| 115 | + else: |
| 116 | + mainpydir = osp.dirname(mainpyfile) |
| 117 | + dirnames = ( |
| 118 | + [mainpydir] + os.environ["PATH"].split(os.pathsep) + ["."] |
| 119 | + ) |
| 120 | + try_file = Mclifns.whence_file(py_file, dirnames) |
| 121 | + |
| 122 | + if osp.isfile(try_file): |
| 123 | + mainpyfile = try_file |
| 124 | + pass |
| 125 | + else: |
| 126 | + # Move onto the except branch |
| 127 | + raise IOError( |
| 128 | + "Python file name embedded in code %s not found" % try_file |
| 129 | + ) |
| 130 | + except: |
| 131 | + try: |
| 132 | + from uncompyle6 import decompile_file |
| 133 | + except ImportError: |
| 134 | + sys.stderr.write("%s: Compiled python file '%s', but uncompyle6 not found\n" |
| 135 | + % (__title__, mainpyfile)) |
| 136 | + sys.exit(1) |
| 137 | + return |
| 138 | + |
| 139 | + short_name = osp.basename(mainpyfile).strip(".pyc") |
| 140 | + fd = tempfile.NamedTemporaryFile( |
| 141 | + suffix=".py", |
| 142 | + prefix=short_name + "_", |
| 143 | + dir=dbg.settings["tempdir"], |
| 144 | + delete=False, |
| 145 | + ) |
| 146 | + try: |
| 147 | + decompile_file(mainpyfile, outstream=fd) |
| 148 | + mainpyfile = fd.name |
| 149 | + fd.close() |
| 150 | + except: |
| 151 | + sys.stderr.write("%s: error uncompyling '%s'\n" |
| 152 | + % (__title__, mainpyfile)) |
| 153 | + sys.exit(1) |
| 154 | + pass |
| 155 | + |
| 156 | + # If mainpyfile is an optimized Python script try to find and |
| 157 | + # use non-optimized alternative. |
| 158 | + mainpyfile_noopt = pyficache.resolve_name_to_path(mainpyfile) |
| 159 | + if mainpyfile != mainpyfile_noopt \ |
| 160 | + and Mfile.readable(mainpyfile_noopt): |
| 161 | + sys.stderr.write("%s: Compiled Python script given and we can't use that.\n" |
| 162 | + % __title__) |
| 163 | + sys.stderr.write("%s: Substituting non-compiled name: %s\n" % |
| 164 | + (__title__, mainpyfile_noopt)) |
| 165 | + mainpyfile = mainpyfile_noopt |
| 166 | + pass |
| 167 | + |
| 168 | + # Replace trepan's dir with script's dir in front of |
| 169 | + # module search path. |
| 170 | + sys.path[0] = dbg.main_dirname = osp.dirname(mainpyfile) |
| 171 | + |
| 172 | + # XXX If a signal has been received we continue in the loop, otherwise |
| 173 | + # the loop exits for some reason. |
| 174 | + dbg.sig_received = False |
| 175 | + |
| 176 | + # if not mainpyfile: |
| 177 | + # print('For now, you need to specify a Python script name!') |
| 178 | + # sys.exit(2) |
| 179 | + # pass |
| 180 | + |
| 181 | + while True: |
| 182 | + |
| 183 | + # Run the debugged script over and over again until we get it |
| 184 | + # right. |
| 185 | + |
| 186 | + try: |
| 187 | + if dbg.program_sys_argv and mainpyfile: |
| 188 | + normal_termination = dbg.run_script(mainpyfile) |
| 189 | + if not normal_termination: |
| 190 | + break |
| 191 | + else: |
| 192 | + dbg.core.execution_status = "No program" |
| 193 | + dbg.core.processor.process_commands() |
| 194 | + pass |
| 195 | + |
| 196 | + dbg.core.execution_status = "Terminated" |
| 197 | + dbg.intf[-1].msg("The program finished - quit or restart") |
| 198 | + dbg.core.processor.process_commands() |
| 199 | + except Mexcept.DebuggerQuit: |
| 200 | + break |
| 201 | + except Mexcept.DebuggerRestart: |
| 202 | + dbg.core.execution_status = "Restart requested" |
| 203 | + if dbg.program_sys_argv: |
| 204 | + sys.argv = list(dbg.program_sys_argv) |
| 205 | + part1 = "Restarting %s with arguments:" % dbg.core.filename(mainpyfile) |
| 206 | + args = " ".join(dbg.program_sys_argv[1:]) |
| 207 | + dbg.intf[-1].msg( |
| 208 | + Mmisc.wrapped_lines(part1, args, dbg.settings["width"]) |
| 209 | + ) |
| 210 | + else: |
| 211 | + break |
| 212 | + except SystemExit: |
| 213 | + # In most cases SystemExit does not warrant a post-mortem session. |
| 214 | + break |
| 215 | + except: |
| 216 | + # FIXME: Should be handled above without this mess |
| 217 | + exception_name = str(sys.exc_info()[0]) |
| 218 | + if exception_name == str(Mexcept.DebuggerQuit): |
| 219 | + break |
| 220 | + elif exception_name == str(Mexcept.DebuggerRestart): |
| 221 | + dbg.core.execution_status = "Restart requested" |
| 222 | + if dbg.program_sys_argv: |
| 223 | + sys.argv = list(dbg.program_sys_argv) |
| 224 | + part1 = "Restarting %s with arguments:" % dbg.core.filename( |
| 225 | + mainpyfile |
| 226 | + ) |
| 227 | + args = " ".join(dbg.program_sys_argv[1:]) |
| 228 | + dbg.intf[-1].msg( |
| 229 | + Mmisc.wrapped_lines(part1, args, dbg.settings["width"]) |
| 230 | + ) |
| 231 | + pass |
| 232 | + else: |
| 233 | + raise |
| 234 | + pass |
| 235 | + |
| 236 | + # Restore old sys.argv |
| 237 | + sys.argv = orig_sys_argv |
| 238 | + return |
| 239 | + |
| 240 | + |
| 241 | +# When invoked as main program, invoke the debugger on a script |
| 242 | +if __name__ == "__main__": |
| 243 | + main() |
| 244 | + pass |
0 commit comments