internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: pwndbg/pwndbg

Issue:
Our Breakpoint class should reset all stop caches
Essentially what I wrote here https://github.com/pwndbg/pwndbg/pull/1799#discussion_r1267211887 - instead of clearing only registers cache, we should clear all caches that were registered to be cleared by the 'stop' event in our Breakpoint class:

https://github.com/pwndbg/pwndbg/blob/cb053dda41c0aadb9e186c596c306ec86773867e/pwndbg/gdblib/bpoint.py#L6-L26

This can be done with this code:
```py
pwndbg.lib.cache._ALL_CACHE_UNTIL_EVENTS['stop'].clear()
```

but we should probably make a function for this instead of using the `_...` variable? :)

Our Breakpoint class should reset all stop caches
Essentially what I wrote here https://github.com/pwndbg/pwndbg/pull/1799#discussion_r1267211887 - instead of clearing only registers cache, we should clear all caches that were registered to be cleared by the 'stop' event in our Breakpoint class:

https://github.com/pwndbg/pwndbg/blob/cb053dda41c0aadb9e186c596c306ec86773867e/pwndbg/gdblib/bpoint.py#L6-L26

This can be done with this code:
```py
pwndbg.lib.cache._ALL_CACHE_UNTIL_EVENTS['stop'].clear()
```

but we should probably make a function for this instead of using the `_...` variable? :)



Discussion:

Resolving patch:
diff --git a/pwndbg/gdblib/bpoint.py b/pwndbg/gdblib/bpoint.py
--- a/pwndbg/gdblib/bpoint.py
+++ b/pwndbg/gdblib/bpoint.py
@@ -15,7 +15,7 @@ class Breakpoint(gdb.Breakpoint):
 
     def stop(self):
         # Clear the cache for the stop event.
-        pwndbg.gdblib.regs.__getattr__.cache.clear()
+        pwndbg.lib.cache.clear_cache("stop")
         return self.should_stop()
 
     def should_stop(self):
diff --git a/pwndbg/lib/cache.py b/pwndbg/lib/cache.py
--- a/pwndbg/lib/cache.py
+++ b/pwndbg/lib/cache.py
@@ -163,3 +163,7 @@ def decorator(*a, **kw):
 def clear_caches() -> None:
     for cache in _ALL_CACHE_UNTIL_EVENTS.values():
         cache.clear()
+
+
+def clear_cache(cache_name) -> None:
+    _ALL_CACHE_UNTIL_EVENTS[cache_name].clear()


Test patch:
Repository: pwndbg/pwndbg

Issue:
Cleanup/optimize break_next_call: compile regex once
The `break_next_call` function which steps through the execution has this functionality that it can stop on a target symbol checked via a regular expression.

However, both the regular expression as well as the string provided to it are constructed on each loop iteration (!). While Python regexes are cached internally, we should not rely on this functionality.

https://github.com/pwndbg/pwndbg/blob/e37591b25d923b1933b7092cacafa51a210d5f5e/pwndbg/gdblib/next.py#L107-L113

Effectively, this issue is just about doing something like:
```py
symbol_re = re.compile(f"{symbol_regex}$")

while ...:
    if ... and symbol_re.match(...): ...
```


Discussion:

Resolving patch:
diff --git a/pwndbg/gdblib/next.py b/pwndbg/gdblib/next.py
--- a/pwndbg/gdblib/next.py
+++ b/pwndbg/gdblib/next.py
@@ -130,6 +130,8 @@ def break_next_interrupt(address=None):
 
 
 def break_next_call(symbol_regex=None):
+    symbol_regex = re.compile(symbol_regex) if symbol_regex else None
+
     while pwndbg.gdblib.proc.alive:
         # Break on signal as it may be a segfault
         if pwndbg.gdblib.proc.stopped_with_signal:
@@ -144,16 +146,15 @@ def break_next_call(symbol_regex=None):
         if capstone.CS_GRP_CALL not in ins.groups:
             continue
 
-        # return call if we don't search for a symbol
-        if not symbol_regex:
-            return ins
-
-        # return call if we match target address
-        if ins.target_const and re.match(f"{symbol_regex}$", hex(ins.target)):
-            return ins
-
-        # return call if we match symbol name
-        if ins.symbol and re.match(f"{symbol_regex}$", ins.symbol):
+        # return call if we:
+        # 1) don't search for a symbol
+        # 2) match target address
+        # 3) match symbol name
+        if (
+            not symbol_regex
+            or (ins.target_const and symbol_regex.match(hex(ins.target)))
+            or (ins.symbol and symbol_regex.match(ins.symbol))
+        ):
             return ins
 
 


Test patch:
Repository: pwndbg/pwndbg

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? 


Discussion:

Resolving patch:
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 @@ def handle(name="Error"):
         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()


Test patch:
Repository: pwndbg/pwndbg

Issue:
Refactor EventWrapper
It seems we can maybe remove EventWrapper class now since we depend on GDB >= 9.2?

https://github.com/pwndbg/pwndbg/issues/1854#issuecomment-1664527390


Discussion:

Resolving patch:
diff --git a/pwndbg/gdblib/events.py b/pwndbg/gdblib/events.py
--- a/pwndbg/gdblib/events.py
+++ b/pwndbg/gdblib/events.py
@@ -67,44 +67,6 @@ def on_stop(self) -> None:
 
 gdb.events.start = StartEvent()
 
-
-class EventWrapper:
-    """
-    Wrapper for GDB events which may not exist on older GDB versions but we still can
-    fire them manually (to invoke them you have to call `invoke_callbacks`).
-    """
-
-    def __init__(self, name: str) -> None:
-        self.name = name
-
-        self._event = getattr(gdb.events, self.name, None)
-        self._is_real_event = self._event is not None
-
-    def connect(self, func) -> None:
-        if self._event is not None:
-            self._event.connect(func)
-
-    def disconnect(self, func) -> None:
-        if self._event is not None:
-            self._event.disconnect(func)
-
-    @property
-    def is_real_event(self) -> bool:
-        return self._is_real_event
-
-    def invoke_callbacks(self) -> None:
-        """
-        As an optimization please don't call this if your GDB has this event (check `.is_real_event`).
-        """
-        for f in registered[self]:
-            f()
-
-
-# Old GDBs doesn't have gdb.events.before_prompt, so we will emulate it using gdb.prompt_hook
-before_prompt_event = EventWrapper("before_prompt")
-gdb.events.before_prompt = before_prompt_event
-
-
 # In order to support reloading, we must be able to re-fire
 # all 'objfile' and 'stop' events.
 registered: dict[Any, list[Callable]] = {
diff --git a/pwndbg/gdblib/prompt.py b/pwndbg/gdblib/prompt.py
--- a/pwndbg/gdblib/prompt.py
+++ b/pwndbg/gdblib/prompt.py
@@ -89,15 +89,4 @@ def set_prompt() -> None:
         prompt = "\x01" + prompt + "\x02"  # SOH + prompt + STX
 
     gdb.execute(f"set prompt {prompt}")
-
-
-if pwndbg.gdblib.events.before_prompt_event.is_real_event:
-    gdb.prompt_hook = initial_hook
-
-else:
-    # Old GDBs doesn't have gdb.events.before_prompt, so we will emulate it using gdb.prompt_hook
-    def extended_prompt_hook(*a):
-        pwndbg.gdblib.events.before_prompt_event.invoke_callbacks()
-        return prompt_hook(*a)
-
-    gdb.prompt_hook = extended_prompt_hook
+gdb.prompt_hook = initial_hook


Test patch:
Repository: pwndbg/pwndbg

Issue:
crash in command start after running sstart
### Description

crash when running start after sstart

### Steps to reproduce

Gdb session history:
```
sstart
start
set exception-verbose on
start
bugreport --run-broweser
```

### My setup

```
Platform: Linux-6.2.0-26-generic-x86_64-with-glibc2.35
OS: Ubuntu 22.04.3 LTS
OS ABI: #26~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jul 13 16:27:29 UTC 2
Architecture: x86_64
Endian: little
Charset: utf-8
Width: 156
Height: 78
Gdb:      12.1
Python:   3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0]
Pwndbg:   2023.07.17 build: e959a47
Capstone: 5.0.1280
Unicorn:  2.0.1
This GDB was configured as follows:
   configure --host=x86_64-linux-gnu --target=x86_64-linux-gnu
	     --with-auto-load-dir=$debugdir:$datadir/auto-load
	     --with-auto-load-safe-path=$debugdir:$datadir/auto-load
	     --with-expat
	     --with-gdb-datadir=/usr/share/gdb (relocatable)
	     --with-jit-reader-dir=/usr/lib/gdb (relocatable)
	     --without-libunwind-ia64
	     --with-lzma
	     --with-babeltrace
	     --with-intel-pt
	     --with-mpfr
	     --with-xxhash
	     --with-python=/usr (relocatable)
	     --with-python-libdir=/usr/lib (relocatable)
	     --with-debuginfod
	     --without-guile
	     --enable-source-highlight
	     --with-separate-debug-dir=/usr/lib/debug (relocatable)
	     --with-system-gdbinit=/etc/gdb/gdbinit
	     --with-system-gdbinit-dir=/etc/gdb/gdbinit.d

("Relocatable" means the directory can be moved with the GDB installation
tree, and GDB will still find it.)

```

![image](https://github.com/pwndbg/pwndbg/assets/93072266/f13bd64a-fff2-46f2-8b1b-df25db23bdfb)



Discussion:
Has this been fixed recently? or not yet?
This hasn't been fixed. I can still reproduce this today.

Resolving patch:
diff --git a/pwndbg/gdblib/symbol.py b/pwndbg/gdblib/symbol.py
--- a/pwndbg/gdblib/symbol.py
+++ b/pwndbg/gdblib/symbol.py
@@ -136,6 +136,9 @@ def address(symbol: str) -> int:
         # this case before calling `gdb.lookup_symbol`
         skipped_exceptions.append("Cannot find thread-local")
 
+        # This reproduced on GDB 12.1 and caused #1878
+        skipped_exceptions.append("symbol requires a frame to compute its value")
+
         if all(x not in str(e) for x in skipped_exceptions):
             raise e
 


Test patch:
Repository: pwndbg/pwndbg

Issue:
Refactor symbol.py skipped exceptions
The list created here: https://github.com/pwndbg/pwndbg/blob/85eb6c8bf9c59280f82f2168048071de104cd9dc/pwndbg/gdblib/symbol.py#L123-L140

should be a global variable and should be created with `(item1, item2, ...)` instead of appending to a list. The comments should still stand.

If anyone wants to go ahead and refactor it, please send a PR :).



Discussion:

Resolving patch:
diff --git a/pwndbg/gdblib/symbol.py b/pwndbg/gdblib/symbol.py
--- a/pwndbg/gdblib/symbol.py
+++ b/pwndbg/gdblib/symbol.py
@@ -24,6 +24,25 @@
 import pwndbg.ida
 import pwndbg.lib.cache
 
+# Symbol lookup only throws exceptions on errors, not if it failed to
+# look up a symbol. We want to raise these errors so we can handle them
+# properly, but there are some we haven't figured out how to fix yet, so
+# we ignore those here
+skipped_exceptions = None
+
+skipped_exceptions = (
+    # This exception is being thrown by the Go typeinfo tests, we should
+    # investigate why this is happening and see if we can explicitly check
+    # for it with `gdb.selected_frame()`
+    "No frame selected",
+    # If we try to look up a TLS variable when there is no TLS, this
+    # exception occurs. Ideally we should come up with a way to check for
+    # this case before calling `gdb.lookup_symbol`
+    "Cannot find thread-local",
+    # This reproduced on GDB 12.1 and caused #1878
+    "Symbol requires a frame to compute its value",
+)
+
 
 def _get_debug_file_directory():
     """
@@ -120,25 +139,6 @@ def address(symbol: str) -> int:
         if symbol_obj:
             return int(symbol_obj.value().address)
     except gdb.error as e:
-        # Symbol lookup only throws exceptions on errors, not if it failed to
-        # lookup a symbol. We want to raise these errors so we can handle them
-        # properly, but there are some we haven't figured out how to fix yet, so
-        # we ignore those here
-        skipped_exceptions = []
-
-        # This is exception is being thrown by the Go typeinfo tests, we should
-        # investigate why this is happening and see if we can explicitly check
-        # for it with `gdb.selected_frame()`
-        skipped_exceptions.append("No frame selected")
-
-        # If we try to look up a TLS variable when there is no TLS, this
-        # exception occurs. Ideally we should come up with a way to check for
-        # this case before calling `gdb.lookup_symbol`
-        skipped_exceptions.append("Cannot find thread-local")
-
-        # This reproduced on GDB 12.1 and caused #1878
-        skipped_exceptions.append("symbol requires a frame to compute its value")
-
         if all(x not in str(e) for x in skipped_exceptions):
             raise e
 


Test patch:
Repository: pwndbg/pwndbg

Issue:
threads context doesnt display current thread?
When I attached to an app with 39 threads, the threads context panel doesn't show me the thread I am currently stopped on.

Seems like some sorting issue. cc: @CptGibbon 


Discussion:
As far as I'm aware it never did this, the command simply prints the threads in reverse order. Though I appreciate this was a feature request in #1904 so I can certainly add it 👍

Resolving patch:
diff --git a/pwndbg/commands/context.py b/pwndbg/commands/context.py
--- a/pwndbg/commands/context.py
+++ b/pwndbg/commands/context.py
@@ -856,15 +856,30 @@ def get_thread_status(thread):
 
 
 def context_threads(with_banner=True, target=sys.stdout, width=None):
+    try:
+        original_thread = gdb.selected_thread()
+    except SystemError:
+        original_thread = None
+
     all_threads = gdb.selected_inferior().threads()[::-1]
-    displayed_threads = all_threads[: int(config_max_threads_display)]
+
+    displayed_threads = []
+
+    if original_thread is not None and original_thread.is_valid():
+        displayed_threads.append(original_thread)
+
+    for thread in all_threads:
+        if len(displayed_threads) >= int(config_max_threads_display):
+            break
+
+        if thread.is_valid() and thread is not original_thread:
+            displayed_threads.append(thread)
+
     num_threads_not_shown = len(all_threads) - len(displayed_threads)
 
     if len(displayed_threads) < 2:
         return []
 
-    original_thread = gdb.selected_thread()
-
     out = [pwndbg.ui.banner(f"threads ({len(all_threads)} total)", target=target, width=width)]
     max_name_length = 0
 
@@ -902,11 +917,12 @@ def context_threads(with_banner=True, target=sys.stdout, width=None):
     if num_threads_not_shown:
         out.append(
             pwndbg.lib.tips.color_tip(
-                f"Not showing {num_threads_not_shown} threads. Use `set context-max-threads <number of threads>` to change this."
+                f"Not showing {num_threads_not_shown} thread(s). Use `set context-max-threads <number of threads>` to change this."
             )
         )
 
-    original_thread.switch()
+    if original_thread is not None and original_thread.is_valid():
+        original_thread.switch()
 
     return out
 


Test patch:
Repository: pwndbg/pwndbg

Issue:
pattern_create & run features
It is annoying to have to have multiple versions of gdb to complete some of my work. I don't understand why this feature hasn't been brought over yet like gdb-peda has implemented. Reversing takes long enough, this would make our lives a little bit easier.

I would like to add the pattern_create feature into pwndbg. As well as run, so that I can quickly create our cyclic values and then run our output (run < payload-100.txt) so we can check the registers in under 10 seconds without restarting the program. 


Discussion:
@nthdeg can u remind us what `pattern_create` do in peda?

I believe we have that as a `cyclic` command?
Ah thank you, I didn't see that function. `pattern_create 50 payload-50.txt` does the same thing but it saves the string in a txt file within the working dir. then you can run the binary `run < payload-50.txt` feeding it the cyclic pattern.

We then can use `pattern_offset 0xaddress` to return the exact offset in bytes. all while not leaving the program. I am not sure if the same workflow exists with other functions?

Resolving patch:
diff --git a/pwndbg/commands/cyclic.py b/pwndbg/commands/cyclic.py
--- a/pwndbg/commands/cyclic.py
+++ b/pwndbg/commands/cyclic.py
@@ -30,6 +30,7 @@
     help="Size of the unique subsequences (defaults to the pointer size for the current arch)",
 )
 
+
 group = parser.add_mutually_exclusive_group(required=False)
 group.add_argument(
     "-l",
@@ -50,9 +51,17 @@
     help="Number of characters to print from the sequence (default: print the entire sequence)",
 )
 
+parser.add_argument(
+    "filename",
+    type=str,
+    help="Name (path) of the file to save the cyclic pattern to",
+    default="",
+    nargs="?",
+)
+
 
 @pwndbg.commands.ArgparsedCommand(parser, command_name="cyclic")
-def cyclic_cmd(alphabet, length, lookup, count=100) -> None:
+def cyclic_cmd(alphabet, length, lookup, count=100, filename="") -> None:
     if length:
         # Convert from gdb.Value
         length = int(length)
@@ -93,5 +102,12 @@ def cyclic_cmd(alphabet, length, lookup, count=100) -> None:
         else:
             print(message.success(f"Found at offset {offset}"))
     else:
-        sequence = cyclic(int(count), alphabet, length)
-        print(sequence.decode())
+        count = int(count)
+        sequence = cyclic(count, alphabet, length)
+
+        if not filename:
+            print(sequence.decode())
+        else:
+            with open(filename, "wb") as f:
+                f.write(sequence)
+                print(f"Written a cyclic sequence of length {count} to file {filename}")


Test patch:
Repository: pwndbg/pwndbg

Issue:
Next flag for malloc_chunk
Opening this FR related to #1059. I don't think this was later implemented so I want to pick this up to get more familiar with codebase.

> Why stop at the next chunk? The chunk after that can be just as important, as can the previous chunk. Maybe a flag that lets you print a user-defined number of next/previous/surrounding chunks would be useful.

I was however wondering on the way to go about flags. Should we keep 3 seperate flags similar to grep (A, B for next, prev and C for surrounding) or just the next X chunks e.g. `--next 3`?


Discussion:
I'd vote to keep it simple and use your latter suggestion of `--next 3`

Resolving patch:
diff --git a/pwndbg/commands/heap.py b/pwndbg/commands/heap.py
--- a/pwndbg/commands/heap.py
+++ b/pwndbg/commands/heap.py
@@ -424,12 +424,16 @@ def top_chunk(addr=None) -> None:
     "-s", "--simple", action="store_true", help="Simply print malloc_chunk struct's contents."
 )
 
+parser.add_argument(
+    "-n", "--next", type=int, default=0, help="Print the next N chunks after the specified address."
+)
+
 
 @pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.HEAP)
 @pwndbg.commands.OnlyWithResolvedHeapSyms
 @pwndbg.commands.OnlyWhenHeapIsInitialized
 @pwndbg.commands.OnlyWhenUserspace
-def malloc_chunk(addr, fake=False, verbose=False, simple=False) -> None:
+def malloc_chunk(addr, fake=False, verbose=False, simple=False, next=0) -> None:
     """Print a malloc_chunk struct's contents."""
     allocator = pwndbg.heap.current
 
@@ -505,6 +509,17 @@ def malloc_chunk(addr, fake=False, verbose=False, simple=False) -> None:
 
     print(" | ".join(headers_to_print) + "\n" + out_fields)
 
+    if next:
+        print(C.banner(f"Next {next} chunk(s):"))
+        for _ in range(next):
+            chunk = chunk.next_chunk()
+
+            if not chunk:
+                print("No next chunk found")
+                break
+
+            malloc_chunk(chunk.address, fake=fake, verbose=verbose, simple=simple)
+
 
 parser = argparse.ArgumentParser(
     formatter_class=argparse.RawTextHelpFormatter,


Test patch:
Repository: pwndbg/pwndbg

Issue:
Incorrect `help set emulate` docstring
TL;DR the docstring here seems to be copied from vmmap configuration, so it has to be fixed:

```
pwndbg> help set emulate
Set Unicorn emulation of code near the current instruction.
emulate can be:
off             - read /proc/$qemu-pid/mem to parse kernel page tables to render vmmap
jumps-only      - use QEMU's `monitor info mem` to render vmmap
on              - disable vmmap rendering; useful if rendering is particularly slow

Note that the page-tables method will require the QEMU kernel process to be on the same machine and within the same PID namespace. Running QEMU kernel and GDB in different Docker containers will not work. Consider running both containers with --pid=host (meaning they will see and so be able to interact with all processes on the machine).
```

Code responsible for it:
https://github.com/pwndbg/pwndbg/blob/dev/pwndbg/disasm/arch.py#L21-L37


Discussion:
cc: @OBarronCS can you fix it? :)

Resolving patch:
diff --git a/pwndbg/disasm/arch.py b/pwndbg/disasm/arch.py
--- a/pwndbg/disasm/arch.py
+++ b/pwndbg/disasm/arch.py
@@ -22,15 +22,16 @@
     "emulate",
     "on",
     """
-Unicorn emulation of code near the current instruction
+Unicorn emulation of code from the current PC register
 """,
     help_docstring="""\
 emulate can be:
-off             - read /proc/$qemu-pid/mem to parse kernel page tables to render vmmap
-jumps-only      - use QEMU's `monitor info mem` to render vmmap
-on              - disable vmmap rendering; useful if rendering is particularly slow
+off             - no emulation is performed
+jumps-only      - emulation is done only to resolve branch instructions
+on              - emulation is done to resolve registers/memory values etc.
 
-Note that the page-tables method will require the QEMU kernel process to be on the same machine and within the same PID namespace. Running QEMU kernel and GDB in different Docker containers will not work. Consider running both containers with --pid=host (meaning they will see and so be able to interact with all processes on the machine).
+Emulation can slow down Pwndbg. Disabling it may improve performance.
+Emulation requires >1GB RAM being available on the system and ability to allocate RWX memory.
 """,
     param_class=gdb.PARAM_ENUM,
     enum_sequence=["on", "off", "jumps-only"],


Test patch: