prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Better exception handling for unmet dependencies
  Some of our users update Pwndbg only by `git pull`ing the newest version and not re-running `setup.sh`. If that happens and when we updated our dependencies recently, the user may end up with broken Pwndbg such as (likely) happened in https://github.com/pwndbg/pwndbg/issues/1790.
  
  We should do the two following things:
  1) When we detect/handle an exception in the [`pwndbg/exception.py:handle` function]( https://github.com/pwndbg/pwndbg/blob/de4acb2f40da3b0c97353a8b680ffa6440346f7b/pwndbg/exception.py#L61-L96), we should check the installed dependencies versions against the required ones and if they do not match, we should inform the user to update them.
  2) We may want to display installed dependencies versions within the `bugreport` command output? 
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pwndbg/exception.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 import functools
  4 import sys
  5 import traceback
  6 
  7 import gdb
  8 
  9 import pwndbg.lib.cache
  10 import pwndbg.lib.stdio
  11 from pwndbg.color import message
  12 from pwndbg.gdblib import config
  13 
  14 with pwndbg.lib.stdio.stdio:
  15     try:
  16         import ipdb as pdb
  17     except ImportError:
  18         import pdb
  19     try:
  20         from rich.console import Console
  21 
  22         _rich_console = Console()
  23     except ImportError:
  24         _rich_console = None
  25 
  26 verbose = config.add_param(
  27     "exception-verbose",
  28     False,
  29     "whether to print a full stacktrace for exceptions raised in Pwndbg commands",
  30 )
  31 debug = config.add_param(
  32     "exception-debugger", False, "whether to debug exceptions raised in Pwndbg commands"
  33 )
  34 
  35 
  36 @pwndbg.lib.cache.cache_until("forever")
  37 def inform_report_issue(exception_msg) -> None:
  38     """
  39     Informs user that he can report an issue.
  40     The use of caching makes it reporting only once for a given exception message.
  41     """
  42     print(
  43         message.notice(
  44             "If that is an issue, you can report it on https://github.com/pwndbg/pwndbg/issues\n"
  45             "(Please don't forget to search if it hasn't been reported before)\n"
  46             "To generate the report and open a browser, you may run "
  47         )
  48         + message.hint("`bugreport --run-browser`")
  49         + message.notice("\nPS: Pull requests are welcome")
  50     )
  51 
  52 
  53 def inform_verbose_and_debug() -> None:
  54     print(
  55         message.notice("For more info invoke `")
  56         + message.hint("set exception-verbose on")
  57         + message.notice("` and rerun the command\nor debug it by yourself with `")
  58         + message.hint("set exception-debugger on")
  59         + message.notice("`")
  60     )
  61 
  62 
  63 def handle(name="Error"):
  64     """Displays an exception to the user, optionally displaying a full traceback
  65     and spawning an interactive post-moretem debugger.
  66 
  67     Notes:
  68         - ``set exception-verbose on`` enables stack traces.
  69         - ``set exception-debugger on`` enables the post-mortem debugger.
  70     """
  71 
  72     # This is for unit tests so they fail on exceptions instead of displaying them.
  73     if getattr(sys, "_pwndbg_unittest_run", False) is True:
  74         E, V, T = sys.exc_info()
  75         e = E(V)
  76         e.__traceback__ = T
  77         raise e
  78 
  79     # Display the error
  80     if debug or verbose:
  81         exception_msg = traceback.format_exc()
  82         if _rich_console:
  83             _rich_console.print_exception()
  84         else:
  85             print(exception_msg)
  86         inform_report_issue(exception_msg)
  87 
  88     else:
  89         exc_type, exc_value, exc_traceback = sys.exc_info()
  90 
  91         print(message.error(f"Exception occurred: {name}: {exc_value} ({exc_type})"))
  92 
  93         inform_verbose_and_debug()
  94 
  95     # Break into the interactive debugger
  96     if debug:
  97         with pwndbg.lib.stdio.stdio:
  98             pdb.post_mortem()
  99 
  100 
  101 @functools.wraps(pdb.set_trace)
  102 def set_trace() -> None:
  103     """Enable sane debugging in Pwndbg by switching to the "real" stdio."""
  104     debugger = pdb.Pdb(
  105         stdin=sys.__stdin__, stdout=sys.__stdout__, skip=["pwndbg.lib.stdio", "pwndbg.exception"]
  106     )
  107     debugger.set_trace()
  108 
  109 
  110 pdb.set_trace = set_trace
  111 
  112 
  113 @config.trigger(verbose, debug)
  114 def update() -> None:
  115     if verbose or debug:
  116         command = "set python print-stack full"
  117     else:
  118         command = "set python print-stack message"
  119 
  120     gdb.execute(command, from_tty=True, to_string=True)
  121
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/pwndbg/exception.py b/pwndbg/exception.py
  --- a/pwndbg/exception.py
  +++ b/pwndbg/exception.py
  @@ -1,10 +1,12 @@
   from __future__ import annotations
   
   import functools
  +import os
   import sys
   import traceback
   
   import gdb
  +import pkg_resources
   
   import pwndbg.lib.cache
   import pwndbg.lib.stdio
  @@ -33,6 +35,41 @@
   )
   
   
  +def inform_unmet_dependencies(errors) -> None:
  +    """
  +    Informs user about unmet dependencies
  +    """
  +    msg = message.error("You appear to have unmet Pwndbg dependencies.\n")
  +    for e in errors:
  +        if isinstance(e, pkg_resources.DistributionNotFound):
  +            msg += message.notice(f"- required {e.args[0]}, but not installed\n")
  +        else:
  +            msg += message.notice(f"- required {e.args[1]}, installed: {e.args[0]}\n")
  +    msg += message.notice("Consider running: ")
  +    msg += message.hint("`setup.sh` ")
  +    msg += message.notice("from Pwndbg project directory.\n")
  +    print(msg)
  +
  +
  +@pwndbg.lib.cache.cache_until("forever")
  +def check_dependencies():
  +    """
  +    Checks if there are any unmet dependencies in requirements.txt
  +    """
  +    project_path = os.path.dirname(os.path.abspath(__file__))
  +    requirements_path = os.path.join(project_path, os.pardir, "requirements.txt")
  +    with open(requirements_path, "r") as f:
  +        errors = []
  +        for line in f.readlines():
  +            try:
  +                pkg_resources.require(line)
  +            except (pkg_resources.VersionConflict, pkg_resources.DistributionNotFound) as e:
  +                errors.append(e)
  +
  +        if errors:
  +            inform_unmet_dependencies(errors)
  +
  +
   @pwndbg.lib.cache.cache_until("forever")
   def inform_report_issue(exception_msg) -> None:
       """
  @@ -76,6 +113,9 @@
           e.__traceback__ = T
           raise e
   
  +    # Check dependencies against requirements.txt and warn user
  +    check_dependencies()
  +
       # Display the error
       if debug or verbose:
           exception_msg = traceback.format_exc()
  
verification_info: {"golden_diff": "diff --git a/pwndbg/exception.py b/pwndbg/exception.py\n--- a/pwndbg/exception.py\n+++ b/pwndbg/exception.py\n@@ -1,10 +1,12 @@\n from __future__ import annotations\n \n import functools\n+import os\n import sys\n import traceback\n \n import gdb\n+import pkg_resources\n \n import pwndbg.lib.cache\n import pwndbg.lib.stdio\n@@ -33,6 +35,41 @@\n )\n \n \n+def inform_unmet_dependencies(errors) -> None:\n+    \"\"\"\n+    Informs user about unmet dependencies\n+    \"\"\"\n+    msg = message.error(\"You appear to have unmet Pwndbg dependencies.\\n\")\n+    for e in errors:\n+        if isinstance(e, pkg_resources.DistributionNotFound):\n+            msg += message.notice(f\"- required {e.args[0]}, but not installed\\n\")\n+        else:\n+            msg += message.notice(f\"- required {e.args[1]}, installed: {e.args[0]}\\n\")\n+    msg += message.notice(\"Consider running: \")\n+    msg += message.hint(\"`setup.sh` \")\n+    msg += message.notice(\"from Pwndbg project directory.\\n\")\n+    print(msg)\n+\n+\n+@pwndbg.lib.cache.cache_until(\"forever\")\n+def check_dependencies():\n+    \"\"\"\n+    Checks if there are any unmet dependencies in requirements.txt\n+    \"\"\"\n+    project_path = os.path.dirname(os.path.abspath(__file__))\n+    requirements_path = os.path.join(project_path, os.pardir, \"requirements.txt\")\n+    with open(requirements_path, \"r\") as f:\n+        errors = []\n+        for line in f.readlines():\n+            try:\n+                pkg_resources.require(line)\n+            except (pkg_resources.VersionConflict, pkg_resources.DistributionNotFound) as e:\n+                errors.append(e)\n+\n+        if errors:\n+            inform_unmet_dependencies(errors)\n+\n+\n @pwndbg.lib.cache.cache_until(\"forever\")\n def inform_report_issue(exception_msg) -> None:\n     \"\"\"\n@@ -76,6 +113,9 @@\n         e.__traceback__ = T\n         raise e\n \n+    # Check dependencies against requirements.txt and warn user\n+    check_dependencies()\n+\n     # Display the error\n     if debug or verbose:\n         exception_msg = traceback.format_exc()\n", "issue": "Better exception handling for unmet dependencies\nSome of our users update Pwndbg only by `git pull`ing the newest version and not re-running `setup.sh`. If that happens and when we updated our dependencies recently, the user may end up with broken Pwndbg such as (likely) happened in https://github.com/pwndbg/pwndbg/issues/1790.\r\n\r\nWe should do the two following things:\r\n1) When we detect/handle an exception in the [`pwndbg/exception.py:handle` function]( https://github.com/pwndbg/pwndbg/blob/de4acb2f40da3b0c97353a8b680ffa6440346f7b/pwndbg/exception.py#L61-L96), we should check the installed dependencies versions against the required ones and if they do not match, we should inform the user to update them.\r\n2) We may want to display installed dependencies versions within the `bugreport` command output? \n", "before_files": [{"content": "from __future__ import annotations\n\nimport functools\nimport sys\nimport traceback\n\nimport gdb\n\nimport pwndbg.lib.cache\nimport pwndbg.lib.stdio\nfrom pwndbg.color import message\nfrom pwndbg.gdblib import config\n\nwith pwndbg.lib.stdio.stdio:\n    try:\n        import ipdb as pdb\n    except ImportError:\n        import pdb\n    try:\n        from rich.console import Console\n\n        _rich_console = Console()\n    except ImportError:\n        _rich_console = None\n\nverbose = config.add_param(\n    \"exception-verbose\",\n    False,\n    \"whether to print a full stacktrace for exceptions raised in Pwndbg commands\",\n)\ndebug = config.add_param(\n    \"exception-debugger\", False, \"whether to debug exceptions raised in Pwndbg commands\"\n)\n\n\n@pwndbg.lib.cache.cache_until(\"forever\")\ndef inform_report_issue(exception_msg) -> None:\n    \"\"\"\n    Informs user that he can report an issue.\n    The use of caching makes it reporting only once for a given exception message.\n    \"\"\"\n    print(\n        message.notice(\n            \"If that is an issue, you can report it on https://github.com/pwndbg/pwndbg/issues\\n\"\n            \"(Please don't forget to search if it hasn't been reported before)\\n\"\n            \"To generate the report and open a browser, you may run \"\n        )\n        + message.hint(\"`bugreport --run-browser`\")\n        + message.notice(\"\\nPS: Pull requests are welcome\")\n    )\n\n\ndef inform_verbose_and_debug() -> None:\n    print(\n        message.notice(\"For more info invoke `\")\n        + message.hint(\"set exception-verbose on\")\n        + message.notice(\"` and rerun the command\\nor debug it by yourself with `\")\n        + message.hint(\"set exception-debugger on\")\n        + message.notice(\"`\")\n    )\n\n\ndef handle(name=\"Error\"):\n    \"\"\"Displays an exception to the user, optionally displaying a full traceback\n    and spawning an interactive post-moretem debugger.\n\n    Notes:\n        - ``set exception-verbose on`` enables stack traces.\n        - ``set exception-debugger on`` enables the post-mortem debugger.\n    \"\"\"\n\n    # This is for unit tests so they fail on exceptions instead of displaying them.\n    if getattr(sys, \"_pwndbg_unittest_run\", False) is True:\n        E, V, T = sys.exc_info()\n        e = E(V)\n        e.__traceback__ = T\n        raise e\n\n    # Display the error\n    if debug or verbose:\n        exception_msg = traceback.format_exc()\n        if _rich_console:\n            _rich_console.print_exception()\n        else:\n            print(exception_msg)\n        inform_report_issue(exception_msg)\n\n    else:\n        exc_type, exc_value, exc_traceback = sys.exc_info()\n\n        print(message.error(f\"Exception occurred: {name}: {exc_value} ({exc_type})\"))\n\n        inform_verbose_and_debug()\n\n    # Break into the interactive debugger\n    if debug:\n        with pwndbg.lib.stdio.stdio:\n            pdb.post_mortem()\n\n\n@functools.wraps(pdb.set_trace)\ndef set_trace() -> None:\n    \"\"\"Enable sane debugging in Pwndbg by switching to the \"real\" stdio.\"\"\"\n    debugger = pdb.Pdb(\n        stdin=sys.__stdin__, stdout=sys.__stdout__, skip=[\"pwndbg.lib.stdio\", \"pwndbg.exception\"]\n    )\n    debugger.set_trace()\n\n\npdb.set_trace = set_trace\n\n\n@config.trigger(verbose, debug)\ndef update() -> None:\n    if verbose or debug:\n        command = \"set python print-stack full\"\n    else:\n        command = \"set python print-stack message\"\n\n    gdb.execute(command, from_tty=True, to_string=True)\n", "path": "pwndbg/exception.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport functools\nimport os\nimport sys\nimport traceback\n\nimport gdb\nimport pkg_resources\n\nimport pwndbg.lib.cache\nimport pwndbg.lib.stdio\nfrom pwndbg.color import message\nfrom pwndbg.gdblib import config\n\nwith pwndbg.lib.stdio.stdio:\n    try:\n        import ipdb as pdb\n    except ImportError:\n        import pdb\n    try:\n        from rich.console import Console\n\n        _rich_console = Console()\n    except ImportError:\n        _rich_console = None\n\nverbose = config.add_param(\n    \"exception-verbose\",\n    False,\n    \"whether to print a full stacktrace for exceptions raised in Pwndbg commands\",\n)\ndebug = config.add_param(\n    \"exception-debugger\", False, \"whether to debug exceptions raised in Pwndbg commands\"\n)\n\n\ndef inform_unmet_dependencies(errors) -> None:\n    \"\"\"\n    Informs user about unmet dependencies\n    \"\"\"\n    msg = message.error(\"You appear to have unmet Pwndbg dependencies.\\n\")\n    for e in errors:\n        if isinstance(e, pkg_resources.DistributionNotFound):\n            msg += message.notice(f\"- required {e.args[0]}, but not installed\\n\")\n        else:\n            msg += message.notice(f\"- required {e.args[1]}, installed: {e.args[0]}\\n\")\n    msg += message.notice(\"Consider running: \")\n    msg += message.hint(\"`setup.sh` \")\n    msg += message.notice(\"from Pwndbg project directory.\\n\")\n    print(msg)\n\n\n@pwndbg.lib.cache.cache_until(\"forever\")\ndef check_dependencies():\n    \"\"\"\n    Checks if there are any unmet dependencies in requirements.txt\n    \"\"\"\n    project_path = os.path.dirname(os.path.abspath(__file__))\n    requirements_path = os.path.join(project_path, os.pardir, \"requirements.txt\")\n    with open(requirements_path, \"r\") as f:\n        errors = []\n        for line in f.readlines():\n            try:\n                pkg_resources.require(line)\n            except (pkg_resources.VersionConflict, pkg_resources.DistributionNotFound) as e:\n                errors.append(e)\n\n        if errors:\n            inform_unmet_dependencies(errors)\n\n\n@pwndbg.lib.cache.cache_until(\"forever\")\ndef inform_report_issue(exception_msg) -> None:\n    \"\"\"\n    Informs user that he can report an issue.\n    The use of caching makes it reporting only once for a given exception message.\n    \"\"\"\n    print(\n        message.notice(\n            \"If that is an issue, you can report it on https://github.com/pwndbg/pwndbg/issues\\n\"\n            \"(Please don't forget to search if it hasn't been reported before)\\n\"\n            \"To generate the report and open a browser, you may run \"\n        )\n        + message.hint(\"`bugreport --run-browser`\")\n        + message.notice(\"\\nPS: Pull requests are welcome\")\n    )\n\n\ndef inform_verbose_and_debug() -> None:\n    print(\n        message.notice(\"For more info invoke `\")\n        + message.hint(\"set exception-verbose on\")\n        + message.notice(\"` and rerun the command\\nor debug it by yourself with `\")\n        + message.hint(\"set exception-debugger on\")\n        + message.notice(\"`\")\n    )\n\n\ndef handle(name=\"Error\"):\n    \"\"\"Displays an exception to the user, optionally displaying a full traceback\n    and spawning an interactive post-moretem debugger.\n\n    Notes:\n        - ``set exception-verbose on`` enables stack traces.\n        - ``set exception-debugger on`` enables the post-mortem debugger.\n    \"\"\"\n\n    # This is for unit tests so they fail on exceptions instead of displaying them.\n    if getattr(sys, \"_pwndbg_unittest_run\", False) is True:\n        E, V, T = sys.exc_info()\n        e = E(V)\n        e.__traceback__ = T\n        raise e\n\n    # Check dependencies against requirements.txt and warn user\n    check_dependencies()\n\n    # Display the error\n    if debug or verbose:\n        exception_msg = traceback.format_exc()\n        if _rich_console:\n            _rich_console.print_exception()\n        else:\n            print(exception_msg)\n        inform_report_issue(exception_msg)\n\n    else:\n        exc_type, exc_value, exc_traceback = sys.exc_info()\n\n        print(message.error(f\"Exception occurred: {name}: {exc_value} ({exc_type})\"))\n\n        inform_verbose_and_debug()\n\n    # Break into the interactive debugger\n    if debug:\n        with pwndbg.lib.stdio.stdio:\n            pdb.post_mortem()\n\n\n@functools.wraps(pdb.set_trace)\ndef set_trace() -> None:\n    \"\"\"Enable sane debugging in Pwndbg by switching to the \"real\" stdio.\"\"\"\n    debugger = pdb.Pdb(\n        stdin=sys.__stdin__, stdout=sys.__stdout__, skip=[\"pwndbg.lib.stdio\", \"pwndbg.exception\"]\n    )\n    debugger.set_trace()\n\n\npdb.set_trace = set_trace\n\n\n@config.trigger(verbose, debug)\ndef update() -> None:\n    if verbose or debug:\n        command = \"set python print-stack full\"\n    else:\n        command = \"set python print-stack message\"\n\n    gdb.execute(command, from_tty=True, to_string=True)\n", "path": "pwndbg/exception.py"}]}