Assorted enhancements and bug fixes to LLDB (#3190)

* Add offsets to symbol names in LLDB

* Disable context line reservations if colors are disabled

* LLDB: More aggresively verify memory writes

* LLDB: Add support for disabling ASLR

* Add `-a` flag to `plt` command to show all symbols

* Start shellcode execution at next aligned instruction address, instead of current PC

* Improve execution speed for the `nextproginstr` command

* When resolving address expressions in windgb commands, try resolving as symbol firt

* LLDB: Relay exceptions from commands

* LLDB: Capture stderr in addition to stdout when capturing command output

* Move disabling of line reservations to LLDB test host

* Update docs
pull/3206/head
Matt. 4 months ago committed by GitHub
parent 735ebbeba2
commit a5d5988020
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -2,7 +2,7 @@
# plt # plt
```text ```text
usage: plt [-h] usage: plt [-h] [-a]
``` ```
@ -12,6 +12,7 @@ Prints any symbols found in Procedure Linkage Table sections if any exist.
|Short|Long|Help| |Short|Long|Help|
| :--- | :--- | :--- | | :--- | :--- | :--- |
|-h|--help|show this help message and exit| |-h|--help|show this help message and exit|
|-a|--all-symbols|Print all symbols, not just those that end in @plt|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. --> <!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ --> <!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -79,6 +79,7 @@ class PwndbgArchitecture(ArchDefinition):
### All subclasses must provide values for the following attributes ### All subclasses must provide values for the following attributes
max_instruction_size: int max_instruction_size: int
instruction_alignment: int
### ###
@ -166,6 +167,7 @@ class PwndbgArchitecture(ArchDefinition):
class AMD64Arch(PwndbgArchitecture): class AMD64Arch(PwndbgArchitecture):
max_instruction_size = 16 max_instruction_size = 16
instruction_alignment = 1
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("x86-64") super().__init__("x86-64")
@ -181,6 +183,7 @@ class i386Arch(PwndbgArchitecture):
""" """
max_instruction_size = 16 max_instruction_size = 16
instruction_alignment = 1
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("i386") super().__init__("i386")
@ -196,6 +199,7 @@ class i8086Arch(PwndbgArchitecture):
""" """
max_instruction_size = 16 max_instruction_size = 16
instruction_alignment = 1
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("i8086") super().__init__("i8086")
@ -207,6 +211,7 @@ class i8086Arch(PwndbgArchitecture):
class ArmArch(PwndbgArchitecture): class ArmArch(PwndbgArchitecture):
max_instruction_size = 4 max_instruction_size = 4
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("arm") super().__init__("arm")
@ -237,6 +242,7 @@ class ArmCortexArch(PwndbgArchitecture):
""" """
max_instruction_size = 4 max_instruction_size = 4
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("armcm") super().__init__("armcm")
@ -257,6 +263,7 @@ class ArmCortexArch(PwndbgArchitecture):
class AArch64Arch(PwndbgArchitecture): class AArch64Arch(PwndbgArchitecture):
max_instruction_size = 4 max_instruction_size = 4
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("aarch64") super().__init__("aarch64")
@ -268,6 +275,7 @@ class AArch64Arch(PwndbgArchitecture):
class PowerPCArch(PwndbgArchitecture): class PowerPCArch(PwndbgArchitecture):
max_instruction_size = 4 max_instruction_size = 4
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("powerpc") super().__init__("powerpc")
@ -279,6 +287,7 @@ class PowerPCArch(PwndbgArchitecture):
class SparcArch(PwndbgArchitecture): class SparcArch(PwndbgArchitecture):
max_instruction_size = 4 max_instruction_size = 4
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("sparc") super().__init__("sparc")
@ -291,6 +300,7 @@ class SparcArch(PwndbgArchitecture):
class RISCV32Arch(PwndbgArchitecture): class RISCV32Arch(PwndbgArchitecture):
max_instruction_size = 22 max_instruction_size = 22
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("rv32") super().__init__("rv32")
@ -302,6 +312,7 @@ class RISCV32Arch(PwndbgArchitecture):
class RISCV64Arch(PwndbgArchitecture): class RISCV64Arch(PwndbgArchitecture):
max_instruction_size = 22 max_instruction_size = 22
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("rv64") super().__init__("rv64")
@ -313,6 +324,7 @@ class RISCV64Arch(PwndbgArchitecture):
class MipsArch(PwndbgArchitecture): class MipsArch(PwndbgArchitecture):
max_instruction_size = 8 max_instruction_size = 8
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("mips") super().__init__("mips")
@ -332,6 +344,7 @@ class MipsArch(PwndbgArchitecture):
class Loongarch64Arch(PwndbgArchitecture): class Loongarch64Arch(PwndbgArchitecture):
max_instruction_size = 4 max_instruction_size = 4
instruction_alignment = 4
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("loongarch64") super().__init__("loongarch64")
@ -343,6 +356,7 @@ class Loongarch64Arch(PwndbgArchitecture):
class S390xArch(PwndbgArchitecture): class S390xArch(PwndbgArchitecture):
max_instruction_size = 6 max_instruction_size = 6
instruction_alignment = 2
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("s390x") super().__init__("s390x")

@ -266,6 +266,7 @@ async def break_on_program_code(ec: pwndbg.dbg_mod.ExecutionController) -> bool:
if proc.stopped_with_signal: if proc.stopped_with_signal:
return False return False
await break_next_ret(ec)
await ec.single_step() await ec.single_step()
for start, end in binary_exec_page_ranges: for start, end in binary_exec_page_ranges:

@ -99,8 +99,21 @@ def _ctx_registers() -> Iterator[int]:
registers = {reg: int(uncached_regs.by_name(reg)) for reg in preserve_set} registers = {reg: int(uncached_regs.by_name(reg)) for reg in preserve_set}
starting_address = registers[register_set.pc] starting_address = registers[register_set.pc]
# Advance by one instruction boundary.
#
# Some debuggers (LLDB) may fail to write to memory if any of the addresses
# being written to overlap the program counter. By aiming at the next valid
# instruction address, we avoid that issue.
shell_starting_address = starting_address + pwndbg.aglib.arch.instruction_alignment
# Failing this means our value for `instruction_alignment` is wrong.
assert shell_starting_address % pwndbg.aglib.arch.instruction_alignment == 0
try: try:
yield starting_address # Jump to the target address in preparation.
setattr(pwndbg.aglib.regs, register_set.pc, shell_starting_address)
yield shell_starting_address
finally: finally:
# Restore the code and the program counter and, if requested, the rest of # Restore the code and the program counter and, if requested, the rest of
# the registers. # the registers.

@ -836,7 +836,7 @@ def sloppy_gdb_parse(s: str) -> int | str:
assert target, "Reached command expression evaluation with no frame or inferior" assert target, "Reached command expression evaluation with no frame or inferior"
try: try:
val = target.evaluate_expression(s) val = pwndbg.aglib.symbol.lookup_symbol(s) or target.evaluate_expression(s)
if val.type.code == pwndbg.dbg_mod.TypeCode.FUNC: if val.type.code == pwndbg.dbg_mod.TypeCode.FUNC:
return int(val.address) return int(val.address)
return int(val) return int(val)

@ -128,13 +128,25 @@ def gotplt() -> None:
# These are derived from this list that GDB recognizes: https://github.com/bminor/binutils-gdb/blob/38d726a24c1a85abdb606e7ab6cefad17872aad7/bfd/elf64-x86-64.c#L5775-L5780 # These are derived from this list that GDB recognizes: https://github.com/bminor/binutils-gdb/blob/38d726a24c1a85abdb606e7ab6cefad17872aad7/bfd/elf64-x86-64.c#L5775-L5780
PLT_SECTION_NAMES = (".plt", ".plt.sec", ".plt.got", ".plt.bnd") PLT_SECTION_NAMES = (".plt", ".plt.sec", ".plt.got", ".plt.bnd")
parser = argparse.ArgumentParser(
description="Prints any symbols found in Procedure Linkage Table sections if any exist.",
)
parser.add_argument(
"-a",
"--all-symbols",
help="Print all symbols, not just those that end in @plt",
action="store_true",
default=False,
)
@pwndbg.commands.Command( @pwndbg.commands.Command(
"Prints any symbols found in Procedure Linkage Table sections if any exist.", parser,
category=CommandCategory.LINUX, category=CommandCategory.LINUX,
) )
@pwndbg.commands.OnlyWithFile @pwndbg.commands.OnlyWithFile
def plt() -> None: def plt(all_symbols: bool = False) -> None:
local_path = pwndbg.aglib.file.get_proc_exe_file() local_path = pwndbg.aglib.file.get_proc_exe_file()
bin_base_addr = 0 bin_base_addr = 0
@ -171,7 +183,7 @@ def plt() -> None:
sections_found.sort(key=lambda x: x[1]) sections_found.sort(key=lambda x: x[1])
for section_name, start, end in sections_found: for section_name, start, end in sections_found:
symbols = get_symbols_in_region(start, end, "@plt") symbols = get_symbols_in_region(start, end, "" if all_symbols else "@plt")
print(message.notice(f"Section {section_name} {start:#x} - {end:#x}:")) print(message.notice(f"Section {section_name} {start:#x} - {end:#x}:"))

@ -92,6 +92,16 @@ class LLDBFrame(pwndbg.dbg_mod.Frame):
*, *,
type: pwndbg.dbg_mod.SymbolLookupType = pwndbg.dbg_mod.SymbolLookupType.ANY, type: pwndbg.dbg_mod.SymbolLookupType = pwndbg.dbg_mod.SymbolLookupType.ANY,
) -> pwndbg.dbg_mod.Value | None: ) -> pwndbg.dbg_mod.Value | None:
# `symbol_name_at_address` encodes offsets as part of the name, handle
# that here.
offset = 0
try:
idx = name.rindex("+")
offset = int(name[idx:], 10)
name = name[:idx]
except ValueError:
pass
# FIXME: how to sanitize symbol name better? # FIXME: how to sanitize symbol name better?
if not re.match(r"^[a-zA-Z0-9_.:@*/$]+$", name): if not re.match(r"^[a-zA-Z0-9_.:@*/$]+$", name):
raise pwndbg.dbg_mod.Error(f"Symbol {name!r} contains invalid characters") raise pwndbg.dbg_mod.Error(f"Symbol {name!r} contains invalid characters")
@ -107,6 +117,9 @@ class LLDBFrame(pwndbg.dbg_mod.Frame):
# This issue occurs on certain architectures (e.g., it works fine on x86_64 but fails on aarch64). # This issue occurs on certain architectures (e.g., it works fine on x86_64 but fails on aarch64).
value = self.proc.lookup_symbol(name, type=type) value = self.proc.lookup_symbol(name, type=type)
if value is not None:
value += offset
return value return value
@override @override
@ -994,9 +1007,15 @@ class LLDBProcess(pwndbg.dbg_mod.Process):
e = lldb.SBError() e = lldb.SBError()
count = self.process.WriteMemory(address, data, e) count = self.process.WriteMemory(address, data, e)
if count < len(data) and not partial: if (count < len(data) or not e.success) and not partial:
raise pwndbg.dbg_mod.Error(f"could not write {len(data)} bytes: {e}") raise pwndbg.dbg_mod.Error(f"could not write {len(data)} bytes: {e}")
# In some instances - eg. writing to the PC - writing may still have
# failed when we get here. Make sure we can read it back, to a point.
readback_len = min(len(data), 64)
if self.read_memory(address, readback_len) != data[:readback_len]:
raise pwndbg.dbg_mod.Error(f"could not write {len(data)} bytes: read-back failed")
# We know some memory got changed. # We know some memory got changed.
self.dbg._trigger_event(pwndbg.dbg_mod.EventType.MEMORY_CHANGED) self.dbg._trigger_event(pwndbg.dbg_mod.EventType.MEMORY_CHANGED)
return count return count
@ -1229,7 +1248,16 @@ class LLDBProcess(pwndbg.dbg_mod.Process):
if not ctx.IsValid() or not ctx.symbol.IsValid(): if not ctx.IsValid() or not ctx.symbol.IsValid():
return None return None
# TODO: In GDB, we return something like `main+0x10`, but in LLDB, we do not. sym_addr = ctx.symbol.addr.GetLoadAddress(self.target)
assert (
sym_addr <= address
), f"LLDB returned an out-of-range address {sym_addr:#x} for a requested symbol with address {address:#x}"
if sym_addr != address:
# Print the symbol name along with an offset value if the address we
# were given does not match up with the symbol exactly.
return f"{ctx.symbol.name}+{address - sym_addr}"
return ctx.symbol.name return ctx.symbol.name
def _resolve_tls_symbol(self, sym: lldb.SBSymbol) -> int | None: def _resolve_tls_symbol(self, sym: lldb.SBSymbol) -> int | None:
@ -1743,6 +1771,9 @@ class LLDB(pwndbg.dbg_mod.Debugger):
# return control to the user. # return control to the user.
controllers: List[Tuple[LLDBProcess, Coroutine[Any, Any, None]]] controllers: List[Tuple[LLDBProcess, Coroutine[Any, Any, None]]]
# Relay used for exceptions originating from commands called through LLDB.
_exception_relay: BaseException | None
@override @override
def setup(self, *args, **kwargs): def setup(self, *args, **kwargs):
import pwnlib.update import pwnlib.update
@ -1753,6 +1784,7 @@ class LLDB(pwndbg.dbg_mod.Debugger):
self.event_handlers = {} self.event_handlers = {}
self.controllers = [] self.controllers = []
self._current_process_is_gdb_remote = False self._current_process_is_gdb_remote = False
self._exception_relay = None
import pwndbg import pwndbg
@ -1782,6 +1814,15 @@ class LLDB(pwndbg.dbg_mod.Debugger):
import pwndbg.dbg.lldb.hooks import pwndbg.dbg.lldb.hooks
def relay_exceptions(self) -> None:
"""
Relay an exception raised during an LLDB command handler.
"""
e = self._exception_relay
self._exception_relay = None
if e is not None:
raise pwndbg.dbg_mod.Error(e)
def _execute_lldb_command(self, command: str) -> str: def _execute_lldb_command(self, command: str) -> str:
result = lldb.SBCommandReturnObject() result = lldb.SBCommandReturnObject()
self.debugger.GetCommandInterpreter().HandleCommand( self.debugger.GetCommandInterpreter().HandleCommand(
@ -1811,11 +1852,15 @@ class LLDB(pwndbg.dbg_mod.Debugger):
pass pass
def __call__(self, _, command, exe_context, result): def __call__(self, _, command, exe_context, result):
debugger.exec_states.append(exe_context) try:
handler(debugger, command, True) debugger.exec_states.append(exe_context)
assert ( handler(debugger, command, True)
debugger.exec_states.pop() == exe_context assert (
), "Execution state mismatch on command handler" debugger.exec_states.pop() == exe_context
), "Execution state mismatch on command handler"
except BaseException as e:
debugger._exception_relay = e
raise
# LLDB is very particular with the object paths it will accept. It is at # LLDB is very particular with the object paths it will accept. It is at
# its happiest when its pulling objects straight off the module that was # its happiest when its pulling objects straight off the module that was

@ -336,7 +336,7 @@ def run(
try: try:
if last_exc is not None: if last_exc is not None:
coroutine.throw(last_exc) action = coroutine.throw(last_exc)
else: else:
action = coroutine.send(last_result) action = coroutine.send(last_result)
except StopIteration: except StopIteration:
@ -386,12 +386,20 @@ def run(
if not action._prompt_silent: if not action._prompt_silent:
print(f"{PROMPT}{action._command}") print(f"{PROMPT}{action._command}")
if action._capture: try:
with TextIOWrapper(BytesIO(), write_through=True) as output: if action._capture:
should_continue = exec_repl_command(action._command, output, dbg, driver, relay) with TextIOWrapper(BytesIO(), write_through=True) as output:
last_result = output.buffer.getvalue() should_continue = exec_repl_command(
else: action._command, output, dbg, driver, relay
should_continue = exec_repl_command(action._command, sys.stdout, dbg, driver, relay) )
last_result = output.buffer.getvalue()
else:
should_continue = exec_repl_command(
action._command, sys.stdout, dbg, driver, relay
)
except BaseException as e:
last_exc = e
continue
if not should_continue: if not should_continue:
last_exc = asyncio.CancelledError() last_exc = asyncio.CancelledError()
@ -409,22 +417,31 @@ def exec_repl_command(
Parses and runs the given command, returning whether the event loop should continue. Parses and runs the given command, returning whether the event loop should continue.
""" """
stdout = None stdout = None
stderr = None
lldb_out = None lldb_out = None
lldb_err = None
try: try:
stdout = sys.stdout stdout = sys.stdout
stderr = sys.stderr
lldb_out = dbg.debugger.GetOutputFile() lldb_out = dbg.debugger.GetOutputFile()
lldb_err = dbg.debugger.GetErrorFile()
sys.stdout = output_to sys.stdout = output_to
dbg.debugger.SetOutputFile( dbg.debugger.SetOutputFile(
lldb.SBFile.Create(output_to, borrow=True, force_io_methods=True) lldb.SBFile.Create(output_to, borrow=True, force_io_methods=True)
) )
dbg.debugger.SetErrorFile(lldb.SBFile.Create(output_to, borrow=True, force_io_methods=True))
return _exec_repl_command(line, output_to.buffer, dbg, driver, relay) return _exec_repl_command(line, output_to.buffer, dbg, driver, relay)
finally: finally:
if stdout is not None: if stdout is not None:
sys.stdout = stdout sys.stdout = stdout
if stderr is not None:
sys.stderr = stderr
if lldb_out is not None: if lldb_out is not None:
dbg.debugger.SetOutputFile(lldb_out) dbg.debugger.SetOutputFile(lldb_out)
if lldb_err is not None:
dbg.debugger.SetErrorFile(lldb_err)
def _exec_repl_command( def _exec_repl_command(
@ -687,6 +704,7 @@ def _exec_repl_command(
# one, or just in a general context. # one, or just in a general context.
if driver.has_process(): if driver.has_process():
driver.run_lldb_command(line, lldb_out_target) driver.run_lldb_command(line, lldb_out_target)
dbg.relay_exceptions()
else: else:
ret = lldb.SBCommandReturnObject() ret = lldb.SBCommandReturnObject()
dbg.debugger.GetCommandInterpreter().HandleCommand(line, ret) dbg.debugger.GetCommandInterpreter().HandleCommand(line, ret)
@ -700,6 +718,7 @@ def _exec_repl_command(
if len(out) > 0: if len(out) > 0:
lldb_out_target.write(out.encode(sys.stdout.encoding, errors="backslashreplace")) lldb_out_target.write(out.encode(sys.stdout.encoding, errors="backslashreplace"))
lldb_out_target.write(b"\n") lldb_out_target.write(b"\n")
dbg.relay_exceptions()
# At this point, the last command might've queued up some execution # At this point, the last command might've queued up some execution
# control procedures for us to chew on. Run them now. # control procedures for us to chew on. Run them now.
@ -957,7 +976,7 @@ def target_create(args: List[str], dbg: LLDB) -> None:
process_launch_ap = argparse.ArgumentParser(add_help=False, prog="process launch") process_launch_ap = argparse.ArgumentParser(add_help=False, prog="process launch")
process_launch_ap.add_argument("-A", "--disable-aslr") process_launch_ap.add_argument("-A", "--disable-aslr", type=_bool_of_string, default=False)
process_launch_ap.add_argument("-C", "--script-class") process_launch_ap.add_argument("-C", "--script-class")
process_launch_ap.add_argument("-E", "--environment", action="append") process_launch_ap.add_argument("-E", "--environment", action="append")
process_launch_ap.add_argument("-P", "--plugin") process_launch_ap.add_argument("-P", "--plugin")
@ -975,7 +994,6 @@ process_launch_ap.add_argument("-v", "--structured-data-value")
process_launch_ap.add_argument("-w", "--working-dir") process_launch_ap.add_argument("-w", "--working-dir")
process_launch_ap.add_argument("run-args", nargs="*") process_launch_ap.add_argument("run-args", nargs="*")
process_launch_unsupported = [ process_launch_unsupported = [
"disable-aslr",
"script-class", "script-class",
"plugin", "plugin",
"arch", "arch",
@ -1032,6 +1050,7 @@ def process_launch(driver: ProcessDriver, relay: EventRelay, args: List[str], db
+ (args.environment if args.environment else []), + (args.environment if args.environment else []),
launch_args, launch_args,
os.getcwd(), os.getcwd(),
args.disable_aslr,
) )
match result: match result:

@ -586,7 +586,11 @@ class ProcessDriver:
return LaunchResultSuccess() return LaunchResultSuccess()
def _launch_remote( def _launch_remote(
self, env: List[str], args: List[str], working_dir: str | None self,
env: List[str],
args: List[str],
working_dir: str | None,
extra_flags: int,
) -> lldb.SBError: ) -> lldb.SBError:
""" """
Launch a process in a remote debugserver. Launch a process in a remote debugserver.
@ -605,7 +609,7 @@ class ProcessDriver:
stdout, stdout,
stderr, stderr,
working_dir, working_dir,
lldb.eLaunchFlagStopAtEntry, lldb.eLaunchFlagStopAtEntry | extra_flags,
True, True,
error, error,
) )
@ -618,6 +622,7 @@ class ProcessDriver:
env: List[str], env: List[str],
args: List[str], args: List[str],
working_dir: str | None, working_dir: str | None,
extra_flags: int,
) -> lldb.SBError: ) -> lldb.SBError:
""" """
Launch a process in the host system. Launch a process in the host system.
@ -634,7 +639,7 @@ class ProcessDriver:
stdout, stdout,
stderr, stderr,
working_dir, working_dir,
lldb.eLaunchFlagStopAtEntry, lldb.eLaunchFlagStopAtEntry | extra_flags,
True, True,
error, error,
) )
@ -671,6 +676,7 @@ class ProcessDriver:
env: List[str], env: List[str],
args: List[str], args: List[str],
working_dir: str | None, working_dir: str | None,
disable_aslr: bool,
) -> LaunchResult: ) -> LaunchResult:
""" """
Launches the process and handles startup events. Always stops on first Launches the process and handles startup events. Always stops on first
@ -678,14 +684,18 @@ class ProcessDriver:
Fires the created() event. Fires the created() event.
""" """
extra_flags = 0
if disable_aslr:
extra_flags |= lldb.eLaunchFlagDisableASLR
if self.has_connection(): if self.has_connection():
result = self._enter(self._launch_remote, env, args, working_dir) result = self._enter(self._launch_remote, env, args, working_dir, extra_flags)
if isinstance(result, LaunchResultError): if isinstance(result, LaunchResultError):
result.disconnected = True result.disconnected = True
return result return result
else: else:
self._prepare_listener_for(target) self._prepare_listener_for(target)
return self._enter(self._launch_local, target, io, env, args, working_dir) return self._enter(self._launch_local, target, io, env, args, working_dir, extra_flags)
def attach(self, target: lldb.SBTarget, info: lldb.SBAttachInfo) -> LaunchResult: def attach(self, target: lldb.SBTarget, info: lldb.SBAttachInfo) -> LaunchResult:
""" """

@ -27,6 +27,7 @@ async def _run(ctrl: Any, outer: Callable[..., Coroutine[Any, Any, None]]) -> No
self.pc = pc self.pc = pc
async def launch(self, binary: Path, args: List[str] = []) -> None: async def launch(self, binary: Path, args: List[str] = []) -> None:
await self.pc.execute("set context-reserve-lines never")
await self.pc.execute(f"target create {binary}") await self.pc.execute(f"target create {binary}")
await self.pc.execute( await self.pc.execute(
"process launch -s -- " + " ".join(shlex.quote(arg) for arg in args) "process launch -s -- " + " ".join(shlex.quote(arg) for arg in args)

Loading…
Cancel
Save