diff --git a/hy/cmdline.py b/hy/cmdline.py
index 08f6c0b3a..74aae1720 100644
--- a/hy/cmdline.py
+++ b/hy/cmdline.py
@@ -13,17 +13,196 @@
 import importlib
 import py_compile
 import runpy
-
+import traceback
 import astor.code_gen
 
 import hy
 from hy.lex import LexException, PrematureEndOfInput, mangle
 from hy.compiler import HyTypeError, hy_compile
-from hy.importer import hy_eval, hy_parse
+from hy.importer import hy_eval, hy_parse, hy_ast_compile_flags
 from hy.completer import completion, Completer
 from hy.macros import macro, require
 from hy.models import HyExpression, HyString, HySymbol
-from hy._compat import builtins, PY3, FileNotFoundError
+from hy._compat import builtins, PY3, FileNotFoundError, str_type
+
+
+class HyPdb(object):
+    """A contextmanager that patches `bdb` and `pdb` modules to make them parse
+    Hy.
+
+    In order to affect the interactive console environment when using `exec`,
+    we create custom versions of `[pb]db` methods that use specific namespace
+    dictionaries.  Also, they force use of the closure's global and local
+    values to slightly reduce the number of functions that need patches.
+    """
+
+    @staticmethod
+    def pdb_compile(src, filename='<stdin>',
+                    mode='single', module_name='cmdline'):
+        hy_tree = hy_parse(src + '\n')
+        ast_root = ast.Interactive if mode == 'single' else ast.Module
+        hy_ast = hy_compile(hy_tree, module_name,
+                            root=ast_root)
+        code = compile(hy_ast, filename, mode, hy_ast_compile_flags)
+        return code
+
+    def __init__(self, ctx_globals=None, ctx_locals=None):
+        self.ctx_globals = ctx_globals
+        self.ctx_locals = ctx_locals
+
+    def pdbpp_getval_or_undefined(self):
+        _pdb = self.pdb
+        def _pdbpp_getval_or_undefined(self, arg):
+            """This is just for `pdb++`"""
+            try:
+                code = HyPdb.pdb_compile(arg)
+                return eval(code, self.curframe.f_globals,
+                            self.curframe.f_locals)
+            except NameError:
+                return _pdb.undefined
+
+    def hy_pdb_default(self):
+        def _hy_pdb_default(self, line):
+            if line[:1] == '!': line = line[1:]
+            locals = self.curframe_locals
+            globals = self.curframe.f_globals
+            try:
+                code = HyPdb.pdb_compile(line + '\n', mode='single')
+                save_stdout = sys.stdout
+                save_stdin = sys.stdin
+                save_displayhook = sys.displayhook
+                try:
+                    sys.stdin = self.stdin
+                    sys.stdout = self.stdout
+                    sys.displayhook = self.displayhook
+                    exec(code, globals, locals)
+                finally:
+                    sys.stdout = save_stdout
+                    sys.stdin = save_stdin
+                    sys.displayhook = save_displayhook
+            except:
+                exc_info = sys.exc_info()[:2]
+                msg = traceback.format_exception_only(*exc_info)[-1].strip()
+                print('***', msg, file=self.stdout)
+        return _hy_pdb_default
+
+    def hy_pdb_getval(self):
+        def _hy_pdb_getval(self, arg):
+            try:
+                code = HyPdb.pdb_compile(arg)
+                return eval(code, self.curframe.f_globals,
+                            self.curframe_locals)
+            except:
+                t, v = sys.exc_info()[:2]
+                if isinstance(t, str):
+                    exc_type_name = t
+                else:
+                    exc_type_name = t.__name__
+
+                print('***', exc_type_name + ':', repr(v), file=self.stdout)
+                raise
+        return _hy_pdb_getval
+
+    def hy_pdb_getval_except(self):
+        _pdb = self.pdb
+        def _hy_pdb_getval_except(self, arg, frame=None):
+            try:
+                code = HyPdb.pdb_compile(arg)
+                if frame is None:
+                    return eval(code, self.curframe.f_globals, self.curframe_locals)
+                else:
+                    return eval(code, frame.f_globals, frame.f_locals)
+            except:
+                exc_info = sys.exc_info()[:2]
+                err = traceback.format_exception_only(*exc_info)[-1].strip()
+                return _pdb._rstr('** raised %s **' % err)
+        return _hy_pdb_getval_except
+
+    def hy_bdb_runeval(self):
+        ctx_globals = self.ctx_globals
+        ctx_locals = self.ctx_locals
+
+        def _hy_bdb_runeval(self, expr, globals=ctx_globals, locals=ctx_locals):
+            return self.run(expr, globals=globals, locals=locals, mode='eval')
+        return _hy_bdb_runeval
+
+    def hy_bdb_run(self):
+        ctx_globals = self.ctx_globals
+        ctx_locals = self.ctx_locals
+
+        _bdb = self.bdb
+        def _hy_bdb_run(self, cmd, globals=ctx_globals, locals=ctx_locals,
+                        mode='exec'):
+            if globals is None:
+                if ctx_globals is None:
+                    import __main__
+                    globals = __main__.__dict__
+                else:
+                    globals = ctx_globals
+            if locals is None:
+                locals = globals if ctx_locals is None else ctx_locals
+            self.reset()
+            if isinstance(cmd, str_type):
+                cmd = HyPdb.pdb_compile(cmd, filename='<string>', mode=mode)
+            sys.settrace(self.trace_dispatch)
+            try:
+                if mode == 'exec':
+                    exec(cmd, globals, locals)
+                else:
+                    return eval(cmd, globals, locals)
+            except _bdb.BdbQuit:
+                pass
+            finally:
+                self.quitting = 1
+                sys.settrace(None)
+        return _hy_bdb_run
+
+    def _swap_versions(self, restore=False):
+        # if hasattr(pdb, 'pdb'):
+        #     pdb.pdb.Pdb.default = _pdb_default if restore else _hy_pdb_default
+        #     pdb.pdb.Pdb._getval = _pdb_getval if restore else _hy_pdb_getval
+        #     if hasattr(pdb.pdb.Pdb, '_getval_except'):
+        #         pdb.pdb.Pdb._getval_except = _pdb_getval_except if restore else _hy_pdb_getval_except
+        # else:
+        #     pdb.Pdb.default = _pdb_default if restore else _hy_pdb_default
+        #     pdb.Pdb._getval = _pdb_getval if restore else _hy_pdb_getval
+        #     if hasattr(pdb.Pdb, '_getval_except'):
+        #         pdb.Pdb._getval_except = _pdb_getval_except if restore else _hy_pdb_getval_except
+
+        self.bdb.Bdb.runeval = self._bdb_runeval if restore else self.hy_bdb_runeval()
+        self.bdb.Bdb.run = self._bdb_run if restore else self.hy_bdb_run()
+        self._old_pdb.Pdb.default = self._pdb_default if restore else self.hy_pdb_default()
+        self._old_pdb.Pdb._getval = self._pdb_getval if restore else self.hy_pdb_getval()
+        if hasattr(self._old_pdb.Pdb, '_getval_except'):
+            self._old_pdb.Pdb._getval_except = self._pdb_getval_except if restore else self.hy_pdb_getval_except()
+
+    def __enter__(self):
+        # Start with unpatched versions
+        if 'bdb' in sys.modules:
+            del sys.modules['bdb']
+        if 'pdb' in sys.modules:
+            del sys.modules['pdb']
+
+        self.bdb = importlib.import_module('bdb')
+        self.pdb = importlib.import_module('pdb')
+
+        # Keep track of the original methods
+        self._bdb_runeval = self.bdb.Bdb.runeval
+        self._bdb_run = self.bdb.Bdb.run
+        # This condition helps accounts for Pdb++
+        self._old_pdb = self.pdb if not hasattr(self.pdb, 'pdb') else self.pdb.pdb
+        self._pdb_getval = self._old_pdb.Pdb._getval
+        self._pdb_default = self._old_pdb.Pdb.default
+        # This method shows up in Python 3.x
+        if hasattr(self._old_pdb.Pdb, '_getval_except'):
+            self._pdb_getval_except = self._old_pdb.Pdb._getval_except
+
+        self._swap_versions(restore=False)
+
+        return self.pdb
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self._swap_versions(restore=True)
 
 
 class HyQuitter(object):
@@ -47,7 +226,7 @@ def __call__(self, code=None):
 builtins.exit = HyQuitter('exit')
 
 
-class HyREPL(code.InteractiveConsole):
+class HyREPL(code.InteractiveConsole, object):
     def __init__(self, spy=False, output_fn=None, locals=None,
                  filename="<input>"):
 
@@ -65,8 +244,7 @@ def __init__(self, spy=False, output_fn=None, locals=None,
             else:
                 self.output_fn = __builtins__[mangle(output_fn)]
 
-        code.InteractiveConsole.__init__(self, locals=locals,
-                                         filename=filename)
+        super(HyREPL, self).__init__(locals=locals, filename=filename)
 
         # Pre-mangle symbols for repl recent results: *1, *2, *3
         self._repl_results_symbols = [mangle("*{}".format(i + 1)) for i in range(3)]
@@ -129,6 +307,10 @@ def ast_callback(main_ast, expr_ast):
             print(output)
         return False
 
+    def interact(self, *args, **kwargs):
+        with HyPdb(ctx_locals=self.locals) as pdb:
+            self.locals['pdb'] = pdb
+            super(HyREPL, self).interact(*args, **kwargs)
 
 @macro("koan")
 def koan_macro(ETname):
diff --git a/tests/resources/bin/pdb.hy b/tests/resources/bin/pdb.hy
new file mode 100644
index 000000000..1021f65c8
--- /dev/null
+++ b/tests/resources/bin/pdb.hy
@@ -0,0 +1,6 @@
+(defn func1 [x]
+  (print "func1")
+  (+ 1 x))
+(defn func2 [x]
+  (print "func2")
+  (func1 x))
diff --git a/tests/test_bin.py b/tests/test_bin.py
index e642f53bc..bac023d21 100644
--- a/tests/test_bin.py
+++ b/tests/test_bin.py
@@ -31,8 +31,10 @@ def run_cmd(cmd, stdin_data=None, expect=0, dontwritebytecode=False):
     else:
         env.pop("PYTHONDONTWRITEBYTECODE", None)
 
-    cmd = shlex.split(cmd)
-    cmd[0] = os.path.join(hy_dir, cmd[0])
+    if not isinstance(cmd, list):
+        cmd = shlex.split(cmd)
+        cmd[0] = os.path.join(hy_dir, cmd[0])
+
     p = subprocess.Popen(cmd,
                          stdin=subprocess.PIPE,
                          stdout=subprocess.PIPE,
@@ -382,3 +384,89 @@ def test_bin_hy_module_no_main():
 def test_bin_hy_sys_executable():
     output, _ = run_cmd("hy -c '(do (import sys) (print sys.executable))'")
     assert output.strip().endswith('/hy')
+
+
+def test_pdb_basics():
+    """Tests for the Hy-compatibility `pdb` patches.
+
+    Basics that we need to confirm:
+        * `pdb.run` will evaluate in the interpreter environment
+        * the `pdb` shell will evaluate Hy
+        * Hy code will appear in the code listings
+
+    Also, these tests should probably be as debugger agnostic as possible.
+    """
+    import textwrap
+
+    def clean_debug_output(output):
+        output = '\n'.join(output.split('\n')[1:]).strip()
+        # Remove debugger prompt
+        output = re.sub(r'\(i?[pP]db.*?\) ', '', output)
+        # Remove ANSI escape codes
+        output = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', output)
+        return output
+
+    commands = textwrap.dedent("""
+    (pdb.run "(setv x 1)")
+    p (int (+ 1 1))
+    !(print "hi")
+    c
+    (assert (= x 1))
+    """).strip()
+
+    output, _ = run_cmd('hy', stdin_data=commands)
+    output = clean_debug_output(output)
+
+    expected_output = textwrap.dedent("""
+    2
+    None
+    hi
+    => =>
+    """).strip()
+
+    assert output == expected_output
+
+    commands = textwrap.dedent("""
+    (import [tests.resources.bin.pdb [*]])
+    (pdb.run "(func2 0)")
+    b func1
+    c
+    l
+    """).strip()
+
+    output, _ = run_cmd('hy', stdin_data=commands)
+    output = clean_debug_output(output)
+
+    output_lines = output.split('\n')
+
+    assert output_lines[0].startswith('Breakpoint 1')
+    assert output_lines[0].endswith('pdb.hy:2')
+
+    break_pnt_linenum = next((i for i, l in enumerate(output_lines)
+                              if l.strip().startswith('->')), None)
+
+    # Make sure we break in the right place
+    assert break_pnt_linenum == 3
+    assert output_lines[break_pnt_linenum].endswith('(print "func1")')
+
+    # Check the debugger src listing against the actual src file
+    import_src = open('tests/resources/bin/pdb.hy', 'r').readlines()
+    assert all(o.endswith(s.rstrip())
+               for s, o in zip(import_src, output_lines[break_pnt_linenum+1:]))
+
+    # Test the post-mortem debugger
+    commands = textwrap.dedent("""
+    (import [tests.resources.bin.pdb [*]])
+    (func2 'a)
+    (pdb.pm)
+    """).strip()
+
+    output, err_output = run_cmd('hy', stdin_data=commands)
+    output = clean_debug_output(output)
+    err_output = clean_debug_output(err_output)
+    output_lines = output.split('\n')
+    break_pnt_line = next((l for l in output_lines
+                           if l.strip().startswith('->')), None)
+
+    assert 'TypeError' in err_output
+    assert break_pnt_line == '-> (+ 1 x))'