mirror of https://github.com/pwndbg/pwndbg
Improve TUI handling and documentation (#2446)
* Fix ansi color truncation in TUI windows All remaining ansi escape codes after the substring of real characters weren't printed. This caused some colors to linger over to the next line. * Add two default TUI layouts One including the sourcecode section and one without. The allows to try the TUI quickly by selecting one of the layouts: `layout pwndbg` or `layout pwndbg_code`. * Don't use \t tabs in context threads section Horizontal scroll in TUI mode would jump around causing visual glitches. Use spaces instead. * Add warning when section is in TUI layout but not in 'context-sections' The section won't be updated automatically on stop. * Update FEATURES docs including screenshot * Don't create TUI windows when GDB is compiled without it Don't stop pwndbg from loading on gdb without tui integration. * Fix redraw of all windows after toggling TUI mode When switching back to TUI mode, the contextoutput would be sent before a TUI window's `render` function was called. This caused some sections to be printed normally in the cmd window instead of being sent to their TUI window. Keep a list of all open context TUI windows and redirect contextoutput for all of them once the first window is rerendered instead of waiting for them to be rendered too. Also fix the _static_enabled class variable usage.
This commit is contained in:
parent
88c363a65e
commit
3506114d5c
15
FEATURES.md
15
FEATURES.md
|
@ -71,14 +71,21 @@ import splitmind
|
|||
end
|
||||
```
|
||||
|
||||
The context sections are available as native gdb TUI windows as well as `pwndbg_[sectionname]` windows.
|
||||
#### GDB TUI
|
||||
The context sections are available as native [GDB TUI](https://sourceware.org/gdb/current/onlinedocs/gdb.html/TUI.html) windows named `pwndbg_[sectionname]`.
|
||||
|
||||
Try creating a layout and selecting it:
|
||||
There are some predefined layouts coming with pwndbg which you can select using `layout pwndbg` or `layout pwndbg_code`.
|
||||
|
||||
To create [your own layout](https://sourceware.org/gdb/current/onlinedocs/gdb.html/TUI-Commands.html) and selecting it use normal `tui new-layout` syntax like:
|
||||
```
|
||||
tui new-layout pwndbg {-horizontal { { -horizontal { pwndbg_code 2 pwndbg_disasm 8 } 2 { { -horizontal pwndbg_legend 8 pwndbg_control 2 } 1 pwndbg_regs 6 pwndbg_stack 6 } 3 } 7 cmd 3 } 3 { pwndbg_backtrace 1 } 1 } 1 status 1
|
||||
layout pwndbg
|
||||
tui new-layout pwndbg_custom {-horizontal { { -horizontal { pwndbg_code 1 pwndbg_disasm 1 } 2 { {-horizontal pwndbg_legend 8 pwndbg_control 2 } 1 pwndbg_regs 6 pwndbg_stack 6 } 3 } 7 cmd 3 } 3 { pwndbg_backtrace 2 pwndbg_threads 1 pwndbg_expressions 2 } 1 } 1 status 1
|
||||
layout pwndbg_custom
|
||||
```
|
||||
|
||||
![](caps/context_tui.png)
|
||||
|
||||
Use `focus cmd` to focus the command window and have the arrow keys scroll through the command history again. `tui disable` to disable TUI mode and go back to CLI mode when running commands with longer output. `ctrl-x + a` toggles between TUI and CLI mode quickly. Hold shift to ignore the TUI mouse integration and use the mouse normally to select text or copy data.
|
||||
|
||||
### Watch Expressions
|
||||
|
||||
You can add expressions to be watched by the context.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 232 KiB |
|
@ -1139,24 +1139,33 @@ def context_threads(with_banner=True, target=sys.stdout, width=None):
|
|||
if len(displayed_threads) < 2:
|
||||
return []
|
||||
|
||||
out = [pwndbg.ui.banner(f"threads ({len(all_threads)} total)", target=target, width=width)]
|
||||
out = (
|
||||
[pwndbg.ui.banner(f"threads ({len(all_threads)} total)", target=target, width=width)]
|
||||
if with_banner
|
||||
else []
|
||||
)
|
||||
max_name_length = 0
|
||||
max_global_num_len = 0
|
||||
|
||||
for thread in displayed_threads:
|
||||
name = thread.name or ""
|
||||
if len(name) > max_name_length:
|
||||
max_name_length = len(name)
|
||||
if len(str(thread.global_num)) > max_global_num_len:
|
||||
max_global_num_len = len(str(thread.global_num))
|
||||
|
||||
for thread in filter(lambda t: t.is_valid(), displayed_threads):
|
||||
selected = " ►" if thread is original_thread else " "
|
||||
name = thread.name if thread.name is not None else ""
|
||||
padding = max_name_length - len(name)
|
||||
name_padding = max_name_length - len(name)
|
||||
global_num_padding = max(2, max_global_num_len - len(str(thread.global_num)))
|
||||
status = get_thread_status(thread)
|
||||
|
||||
line = (
|
||||
f" {selected} {thread.global_num}\t"
|
||||
f" {selected} {thread.global_num} "
|
||||
f"{' ' * global_num_padding}"
|
||||
f'"{pwndbg.color.cyan(name)}" '
|
||||
f'{" " * padding}'
|
||||
f'{" " * name_padding}'
|
||||
f"{status}: "
|
||||
)
|
||||
|
||||
|
|
|
@ -997,6 +997,8 @@ class GDB(pwndbg.dbg_mod.Debugger):
|
|||
except gdb.error:
|
||||
pass
|
||||
|
||||
pwndbg.gdblib.tui.setup()
|
||||
|
||||
# Reading Comment file
|
||||
from pwndbg.commands import comments
|
||||
|
||||
|
|
|
@ -1,4 +1,42 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import gdb
|
||||
|
||||
import pwndbg.gdblib.tui.context
|
||||
import pwndbg.gdblib.tui.control
|
||||
|
||||
|
||||
def setup() -> None:
|
||||
tui_layouts = [
|
||||
(
|
||||
"tui new-layout pwndbg "
|
||||
"{-horizontal "
|
||||
" { "
|
||||
" { -horizontal "
|
||||
" { pwndbg_disasm 1 } 2 "
|
||||
" { "
|
||||
" { -horizontal pwndbg_legend 8 pwndbg_control 2 } 1 pwndbg_regs 6 pwndbg_stack 6 "
|
||||
" } 3 "
|
||||
" } 7 cmd 3 "
|
||||
" } 3 { pwndbg_backtrace 2 pwndbg_threads 1 pwndbg_expressions 2 } 1 "
|
||||
"} 1 status 1"
|
||||
),
|
||||
(
|
||||
"tui new-layout pwndbg_code "
|
||||
"{-horizontal "
|
||||
" { "
|
||||
" { -horizontal "
|
||||
" { pwndbg_code 1 pwndbg_disasm 1 } 2 "
|
||||
" { "
|
||||
" { -horizontal pwndbg_legend 8 pwndbg_control 2 } 1 pwndbg_regs 6 pwndbg_stack 6 "
|
||||
" } 3 "
|
||||
" } 7 cmd 3 "
|
||||
" } 3 { pwndbg_backtrace 2 pwndbg_threads 1 pwndbg_expressions 2 } 1 "
|
||||
"} 1 status 1"
|
||||
),
|
||||
]
|
||||
for layout in tui_layouts:
|
||||
try:
|
||||
gdb.execute(layout)
|
||||
except gdb.error:
|
||||
pass
|
||||
|
|
|
@ -7,6 +7,8 @@ from typing import Pattern
|
|||
|
||||
import gdb
|
||||
|
||||
import pwndbg
|
||||
from pwndbg.color import message
|
||||
from pwndbg.commands.context import context
|
||||
from pwndbg.commands.context import context_sections
|
||||
from pwndbg.commands.context import contextoutput
|
||||
|
@ -26,7 +28,8 @@ class ContextTUIWindow:
|
|||
_ansi_escape_regex: Pattern[str]
|
||||
_enabled: bool
|
||||
|
||||
_static_enabled = False
|
||||
_static_enabled: bool = True
|
||||
_context_windows: List[ContextTUIWindow] = []
|
||||
|
||||
def __init__(self, tui_window: "gdb.TuiWindow", section: str) -> None:
|
||||
self._tui_window = tui_window
|
||||
|
@ -43,8 +46,10 @@ class ContextTUIWindow:
|
|||
self._enabled = False
|
||||
self._enable()
|
||||
gdb.events.before_prompt.connect(self._before_prompt_listener)
|
||||
ContextTUIWindow._context_windows.append(self)
|
||||
|
||||
def close(self) -> None:
|
||||
ContextTUIWindow._context_windows.remove(self)
|
||||
if self._enabled:
|
||||
self._disable()
|
||||
gdb.events.before_prompt.disconnect(self._before_prompt_listener)
|
||||
|
@ -53,6 +58,19 @@ class ContextTUIWindow:
|
|||
# render is called again after the TUI was disabled
|
||||
self._verify_enabled_state()
|
||||
|
||||
if (
|
||||
not self._lines
|
||||
and self._section != "legend"
|
||||
and self._section not in str(pwndbg.config.context_sections)
|
||||
):
|
||||
self._tui_window.write(
|
||||
message.warn(
|
||||
f"Section '{self._section}' is not in 'context-sections' and won't be updated automatically."
|
||||
),
|
||||
True,
|
||||
)
|
||||
return
|
||||
|
||||
height = self._tui_window.height
|
||||
width = self._tui_window.width
|
||||
start = self._vscroll_start
|
||||
|
@ -93,12 +111,10 @@ class ContextTUIWindow:
|
|||
self.render()
|
||||
|
||||
def _enable(self):
|
||||
_static_enabled = True
|
||||
self._update()
|
||||
self._enabled = True
|
||||
|
||||
def _disable(self):
|
||||
_static_enabled = False
|
||||
self._old_width = 0
|
||||
resetcontextoutput(self._section)
|
||||
self._enabled = False
|
||||
|
@ -128,13 +144,15 @@ class ContextTUIWindow:
|
|||
is_valid = self._tui_window.is_valid()
|
||||
if is_valid:
|
||||
if not self._enabled:
|
||||
should_trigger_context = not self._static_enabled
|
||||
self._enable()
|
||||
if should_trigger_context and gdb.selected_inferior().pid:
|
||||
for context_window in ContextTUIWindow._context_windows:
|
||||
context_window._enable()
|
||||
if not ContextTUIWindow._static_enabled and pwndbg.dbg.selected_inferior().alive():
|
||||
context()
|
||||
ContextTUIWindow._static_enabled = True
|
||||
else:
|
||||
if self._enabled:
|
||||
self._disable()
|
||||
ContextTUIWindow._static_enabled = False
|
||||
return is_valid
|
||||
|
||||
def _ansi_substr(self, line: str, start_char: int, end_char: int) -> str:
|
||||
|
@ -145,12 +163,12 @@ class ContextTUIWindow:
|
|||
colored_idx = 0
|
||||
char_count = 0
|
||||
while colored_idx < len(line):
|
||||
c = line[colored_end_idx]
|
||||
c = line[colored_idx]
|
||||
# collect all ansi escape sequences before the start of the colored substring
|
||||
# as well as after the end of the colored substring
|
||||
# skip them while counting the characters to slice
|
||||
if c == "\x1b":
|
||||
m = self._ansi_escape_regex.match(line[colored_end_idx:])
|
||||
m = self._ansi_escape_regex.match(line[colored_idx:])
|
||||
if m:
|
||||
colored_idx += m.end()
|
||||
if char_count < start_char:
|
||||
|
@ -175,10 +193,11 @@ class ContextTUIWindow:
|
|||
)
|
||||
|
||||
|
||||
sections = ["legend"] + [
|
||||
if hasattr(gdb, "register_window_type"):
|
||||
sections = ["legend"] + [
|
||||
section.__name__.replace("context_", "") for section in context_sections.values()
|
||||
]
|
||||
for section_name in sections:
|
||||
]
|
||||
for section_name in sections:
|
||||
# https://github.com/python/mypy/issues/12557
|
||||
target_func: Callable[..., gdb._Window] = (
|
||||
lambda window, section_name=section_name: ContextTUIWindow(window, section_name)
|
||||
|
|
|
@ -47,6 +47,7 @@ TIPS: List[str] = [
|
|||
"Need to `mmap` or `mprotect` memory in the debugee? Use commands with the same name to inject and run such syscalls",
|
||||
"Use `hi` to see if a an address belongs to a glibc heap chunk",
|
||||
"Use `contextprev` and `contextnext` to display a previous context output again without scrolling",
|
||||
"Try splitting the context output into multiple TUI windows using `layout pwndbg` (`tui disable` or `ctrl-x + a` to go back to CLI mode)",
|
||||
]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue