Skip to content

libdebug.debugger.debugger

Debugger

The Debugger class is the main class of libdebug. It contains all the methods needed to run and interact with the process.

Source code in libdebug/debugger/debugger.py
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
class Debugger:
    """The Debugger class is the main class of `libdebug`. It contains all the methods needed to run and interact with the process."""

    _sentinel: object = object()
    """A sentinel object."""

    _internal_debugger: InternalDebugger
    """The internal debugger object."""

    _previous_argv: list[str]
    """A copy of the previous argv state, used internally to detect changes to argv[0]."""

    def __init__(self: Debugger) -> None:
        pass

    def post_init_(self: Debugger, internal_debugger: InternalDebugger) -> None:
        """Do not use this constructor directly. Use the `debugger` function instead."""
        self._internal_debugger = internal_debugger
        self._internal_debugger.start_up()

        # We need to install the proper callbacks on the ArgumentList
        self._configure_argument_list(self._internal_debugger.argv)
        self._configure_env_dict()

    def run(self: Debugger, timeout: float = -1, redirect_pipes: bool = True) -> PipeManager | None:
        """Starts the process and waits for it to stop.

        Args:
            timeout (float): The timeout for the process to run. If -1, the process will run indefinitely.
            redirect_pipes (bool): Whether to hook and redirect the pipes of the process to a PipeManager.
        """
        return self._internal_debugger.run(timeout, redirect_pipes)

    def attach(self: Debugger, pid: int) -> None:
        """Attaches to an existing process."""
        self._internal_debugger.attach(pid)

    def detach(self: Debugger) -> None:
        """Detaches from the process."""
        self._internal_debugger.detach()

    def kill(self: Debugger) -> None:
        """Kills the process."""
        self._internal_debugger.kill()

    def terminate(self: Debugger) -> None:
        """Interrupts the process, kills it and then terminates the background thread.

        The debugger object will not be usable after this method is called.
        This method should only be called to free up resources when the debugger object is no longer needed.
        """
        self._internal_debugger.terminate()

    def cont(self: Debugger) -> None:
        """Continues the process."""
        self._internal_debugger.cont()

    def interrupt(self: Debugger) -> None:
        """Interrupts the process."""
        self._internal_debugger.interrupt()

    def wait(self: Debugger) -> None:
        """Waits for the process to stop."""
        self._internal_debugger.wait()

    def print_maps(self: Debugger) -> None:
        """Prints the memory maps of the process."""
        liblog.warning("The `print_maps` method is deprecated. Use `d.pprint_maps` instead.")
        self._internal_debugger.pprint_maps()

    def pprint_maps(self: Debugger) -> None:
        """Prints the memory maps of the process."""
        self._internal_debugger.pprint_maps()

    def resolve_symbol(self: Debugger, symbol: str, file: str = "binary") -> int:
        """Resolves the address of the specified symbol.

        Args:
            symbol (str): The symbol to resolve.
            file (str): The backing file to resolve the symbol in. Defaults to "binary"

        Returns:
            int: The address of the symbol.
        """
        return self._internal_debugger.resolve_symbol(symbol, file)

    @property
    def symbols(self: Debugger) -> SymbolList[Symbol]:
        """Get the symbols of the process."""
        return self._internal_debugger.symbols

    def breakpoint(
        self: Debugger,
        position: int | str,
        hardware: bool = False,
        condition: str = "x",
        length: int = 1,
        callback: None | bool | Callable[[ThreadContext, Breakpoint], None] = None,
        file: str = "hybrid",
    ) -> Breakpoint:
        """Sets a breakpoint at the specified location.

        Args:
            position (int | bytes): The location of the breakpoint.
            hardware (bool, optional): Whether the breakpoint should be hardware-assisted or purely software. Defaults to False.
            condition (str, optional): The trigger condition for the breakpoint. Defaults to None.
            length (int, optional): The length of the breakpoint. Only for watchpoints. Defaults to 1.
            callback (None | bool | Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the breakpoint is hit. If True, an empty callback will be set. Defaults to None.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        return self._internal_debugger.breakpoint(position, hardware, condition, length, callback, file)

    def watchpoint(
        self: Debugger,
        position: int | str,
        condition: str = "w",
        length: int = 1,
        callback: None | bool | Callable[[ThreadContext, Breakpoint], None] = None,
        file: str = "hybrid",
    ) -> Breakpoint:
        """Sets a watchpoint at the specified location. Internally, watchpoints are implemented as breakpoints.

        Args:
            position (int | bytes): The location of the breakpoint.
            condition (str, optional): The trigger condition for the watchpoint (either "w", "rw" or "x"). Defaults to "w".
            length (int, optional): The size of the word in being watched (1, 2, 4 or 8). Defaults to 1.
            callback (None | bool | Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the watchpoint is hit. If True, an empty callback will be set. Defaults to None.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        return self._internal_debugger.breakpoint(
            position,
            hardware=True,
            condition=condition,
            length=length,
            callback=callback,
            file=file,
        )

    def catch_signal(
        self: Debugger,
        signal: int | str,
        callback: None | bool | Callable[[ThreadContext, SignalCatcher], None] = None,
        recursive: bool = False,
    ) -> SignalCatcher:
        """Catch a signal in the target process.

        Args:
            signal (int | str): The signal to catch. If "*", "ALL", "all" or -1 is passed, all signals will be caught.
            callback (None | bool | Callable[[ThreadContext, SignalCatcher], None], optional): A callback to be called when the signal is caught. If True, an empty callback will be set. Defaults to None.
            recursive (bool, optional): Whether, when the signal is hijacked with another one, the signal catcher associated with the new signal should be considered as well. Defaults to False.

        Returns:
            SignalCatcher: The SignalCatcher object.
        """
        return self._internal_debugger.catch_signal(signal, callback, recursive)

    def hijack_signal(
        self: Debugger,
        original_signal: int | str,
        new_signal: int | str,
        recursive: bool = False,
    ) -> SyscallHandler:
        """Hijack a signal in the target process.

        Args:
            original_signal (int | str): The signal to hijack. If "*", "ALL", "all" or -1 is passed, all signals will be hijacked.
            new_signal (int | str): The signal to hijack the original signal with.
            recursive (bool, optional): Whether, when the signal is hijacked with another one, the signal catcher associated with the new signal should be considered as well. Defaults to False.

        Returns:
            SignalCatcher: The SignalCatcher object.
        """
        return self._internal_debugger.hijack_signal(original_signal, new_signal, recursive)

    def handle_syscall(
        self: Debugger,
        syscall: int | str,
        on_enter: None | bool | Callable[[ThreadContext, SyscallHandler], None] = None,
        on_exit: None | bool | Callable[[ThreadContext, SyscallHandler], None] = None,
        recursive: bool = False,
    ) -> SyscallHandler:
        """Handle a syscall in the target process.

        Args:
            syscall (int | str): The syscall name or number to handle. If "*", "ALL", "all" or -1 is passed, all syscalls will be handled.
            on_enter (None | bool |Callable[[ThreadContext, SyscallHandler], None], optional): The callback to execute when the syscall is entered. If True, an empty callback will be set. Defaults to None.
            on_exit (None | bool | Callable[[ThreadContext, SyscallHandler], None], optional): The callback to execute when the syscall is exited. If True, an empty callback will be set. Defaults to None.
            recursive (bool, optional): Whether, when the syscall is hijacked with another one, the syscall handler associated with the new syscall should be considered as well. Defaults to False.

        Returns:
            SyscallHandler: The SyscallHandler object.
        """
        return self._internal_debugger.handle_syscall(syscall, on_enter, on_exit, recursive)

    def hijack_syscall(
        self: Debugger,
        original_syscall: int | str,
        new_syscall: int | str,
        recursive: bool = False,
        **kwargs: int,
    ) -> SyscallHandler:
        """Hijacks a syscall in the target process.

        Args:
            original_syscall (int | str): The syscall name or number to hijack. If "*", "ALL", "all" or -1 is passed, all syscalls will be hijacked.
            new_syscall (int | str): The syscall name or number to hijack the original syscall with.
            recursive (bool, optional): Whether, when the syscall is hijacked with another one, the syscall handler associated with the new syscall should be considered as well. Defaults to False.
            **kwargs: (int, optional): The arguments to pass to the new syscall.

        Returns:
            SyscallHandler: The SyscallHandler object.
        """
        return self._internal_debugger.hijack_syscall(original_syscall, new_syscall, recursive, **kwargs)

    def gdb(
        self: Debugger,
        migrate_breakpoints: bool = True,
        open_in_new_process: bool = True,
        blocking: bool = True,
    ) -> GdbResumeEvent:
        """Migrates the current debugging session to GDB.

        Args:
            migrate_breakpoints (bool): Whether to migrate over the breakpoints set in libdebug to GDB.
            open_in_new_process (bool): Whether to attempt to open GDB in a new process instead of the current one.
            blocking (bool): Whether to block the script until GDB is closed.
        """
        return self._internal_debugger.gdb(migrate_breakpoints, open_in_new_process, blocking)

    @property
    def is_in_gdb(self: Debugger) -> bool:
        """Returns whether the process is in GDB."""
        return self._internal_debugger._is_migrated_to_gdb

    def wait_for_gdb(self: Debugger) -> None:
        """Waits for the GDB process to migrate back to libdebug."""
        self._internal_debugger.wait_for_gdb()

    def r(self: Debugger, redirect_pipes: bool = True) -> PipeManager | None:
        """Alias for the `run` method.

        Starts the process and waits for it to stop.

        Args:
            redirect_pipes (bool): Whether to hook and redirect the pipes of the process to a PipeManager.
        """
        return self._internal_debugger.run(redirect_pipes)

    def c(self: Debugger) -> None:
        """Alias for the `cont` method.

        Continues the process.
        """
        self._internal_debugger.cont()

    def int(self: Debugger) -> None:
        """Alias for the `interrupt` method.

        Interrupts the process.
        """
        self._internal_debugger.interrupt()

    def w(self: Debugger) -> None:
        """Alias for the `wait` method.

        Waits for the process to stop.
        """
        self._internal_debugger.wait()

    def bp(
        self: Debugger,
        position: int | str,
        hardware: bool = False,
        condition: str = "x",
        length: int = 1,
        callback: None | Callable[[ThreadContext, Breakpoint], None] = None,
        file: str = "hybrid",
    ) -> Breakpoint:
        """Alias for the `breakpoint` method.

        Args:
            position (int | bytes): The location of the breakpoint.
            hardware (bool, optional): Whether the breakpoint should be hardware-assisted or purely software. Defaults to False.
            condition (str, optional): The trigger condition for the breakpoint. Defaults to None.
            length (int, optional): The length of the breakpoint. Only for watchpoints. Defaults to 1.
            callback (Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the breakpoint is hit. Defaults to None.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        return self._internal_debugger.breakpoint(position, hardware, condition, length, callback, file)

    def wp(
        self: Debugger,
        position: int | str,
        condition: str = "w",
        length: int = 1,
        callback: None | Callable[[ThreadContext, Breakpoint], None] = None,
        file: str = "hybrid",
    ) -> Breakpoint:
        """Alias for the `watchpoint` method.

        Sets a watchpoint at the specified location. Internally, watchpoints are implemented as breakpoints.

        Args:
            position (int | bytes): The location of the breakpoint.
            condition (str, optional): The trigger condition for the watchpoint (either "w", "rw" or "x"). Defaults to "w".
            length (int, optional): The size of the word in being watched (1, 2, 4 or 8). Defaults to 1.
            callback (Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the watchpoint is hit. Defaults to None.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        return self._internal_debugger.breakpoint(
            position,
            hardware=True,
            condition=condition,
            length=length,
            callback=callback,
            file=file,
        )

    @property
    def arch(self: Debugger) -> str:
        """Get the architecture of the process."""
        return self._internal_debugger.arch

    @arch.setter
    def arch(self: Debugger, value: str) -> None:
        """Set the architecture of the process."""
        self._internal_debugger.arch = map_arch(value)

    def _configure_argument_list(self: Debugger, argv: ArgumentList) -> None:
        """Sets up the ArgumentList with the before/after callbacks, and freezes argv[0] if needed."""
        # If the user has not specified a different path, and argv is not empty, we should freeze argv[0]
        if not self._internal_debugger._has_path_different_from_argv0 and argv and argv[0]:
            argv.prevent_empty = True
        else:
            argv.prevent_empty = False

        # We register a _before_callback that stores a copy of the current argv state
        def _before_callback(_: list[str]) -> None:
            """Store a copy of the current argv state."""
            # Changing argv is not allowed while the process is being debugged.
            if self._internal_debugger.is_debugging:
                raise RuntimeError("Cannot change argv while the process is running. Please kill it first.")

            self._previous_argv = list(self._internal_debugger.argv) if self._internal_debugger.argv else []

        # The _after callback should check if argv[0] has changed and update the path accordingly
        def _after_callback(new_argv: list[str]) -> None:
            """An after callback that updates the path if argv[0] has changed."""
            if not hasattr(self, "_previous_argv"):
                raise RuntimeError("The _previous_argv attribute is not set. This should not happen.")

            try:
                if (
                    not self._internal_debugger._has_path_different_from_argv0
                    and new_argv
                    and new_argv[0] != self._previous_argv[0]
                ):
                    self._internal_debugger.clear_all_caches()
                    # Changing path can also change the architecture, so we need to update it
                    resolved_path = resolve_argv_path(new_argv[0])
                    self.arch = elf_architecture(resolved_path)
                    self._internal_debugger.path = resolved_path
            except Exception:
                # We revert to the previous argv state if something goes wrong
                self._internal_debugger.argv = ArgumentList(self._previous_argv)
                raise

        # Set the callbacks on the ArgumentList
        argv.set_callbacks(_before_callback, _after_callback)

    @property
    def argv(self: Debugger) -> ArgumentList:
        """The command line arguments of the debugged process."""
        self._internal_debugger._ensure_process_stopped()
        return self._internal_debugger.argv

    @argv.setter
    def argv(self: Debugger, value: str | list[str] | ArgumentList) -> None:
        """Set the command line arguments of the debugged process."""
        self._internal_debugger._ensure_process_stopped()

        # Changing argv is not allowed while the process is being debugged.
        if self._internal_debugger.is_debugging:
            raise RuntimeError("Cannot change argv while the process is running. Please kill it first.")

        if not isinstance(value, str | list | ArgumentList):
            raise TypeError("argv must be a string or a list of strings")
        if isinstance(value, str):
            value = ArgumentList([value])
        elif isinstance(value, list):
            value = ArgumentList(value)

        # We need to install on the ArgumentList the proper callbacks
        self._configure_argument_list(value)

        # We have to check whether argv[0] has changed
        # if so, we should invalidate everything and resolve the path again
        # but that should be done only if path depended on argv[0]
        if (
            not self._internal_debugger._has_path_different_from_argv0
            and self._internal_debugger.argv
            and value[0] != self._internal_debugger.argv[0]
        ):
            self._internal_debugger.clear_all_caches()
            # Changing path can also change the architecture, so we need to update it
            resolved_path = resolve_argv_path(value[0])
            self.arch = elf_architecture(resolved_path)
            self._internal_debugger.path = resolved_path

        self._internal_debugger.argv = value

    def _configure_env_dict(self: Debugger) -> None:
        """Sets up the EnvDict with the before callback."""
        # We register a _before_callback that ensure that the process
        # is not being debugged when the environment is changed
        def _before_callback() -> None:
            """Ensure that the process is not being debugged when the environment is changed."""
            # Changing env is not allowed while the process is being debugged.
            if self._internal_debugger.is_debugging:
                raise RuntimeError("Cannot change env while the process is running. Please kill it first.")

        if self._internal_debugger.env is not None:
            # If the env is already set, we just need to set the callback
            self._internal_debugger.env.set_callback(_before_callback)

    @property
    def env(self: Debugger) -> EnvDict | None:
        """The environment variables of the debugged process."""
        self._internal_debugger._ensure_process_stopped()
        return self._internal_debugger.env

    @env.setter
    def env(self: Debugger, value: dict[str, str] | None) -> None:
        """Set the environment variables of the debugged process."""
        self._internal_debugger._ensure_process_stopped()

        # Changing env is not allowed while the process is being debugged.
        if self._internal_debugger.is_debugging:
            raise RuntimeError("Cannot change env while the process is running. Please kill it first.")

        if value is not None and not isinstance(value, dict):
            raise TypeError("env must be a dictionary or None")

        self._internal_debugger.env = EnvDict(value) if value is not None else None
        self._configure_env_dict()

    @property
    def path(self: Debugger) -> str:
        """The resolved path to the debugged binary."""
        self._internal_debugger._ensure_process_stopped()
        return self._internal_debugger.path

    @path.setter
    def path(self: Debugger, value: str) -> None:
        """Set the path to the debugged binary."""
        self._internal_debugger._ensure_process_stopped()
        if self._internal_debugger.is_debugging:
            raise RuntimeError("Cannot change path while the process is running. Please kill it first.")

        if not isinstance(value, str):
            raise TypeError("path must be a string")

        self._internal_debugger.clear_all_caches()

        # resolve_argv_path can fail if the path is not valid
        resolved_path = resolve_argv_path(value)

        # Changing path can also change the architecture, so we need to update it
        self.arch = elf_architecture(resolved_path)
        self._internal_debugger.path = resolved_path

        # We can also unfreeze argv[0] if it was frozen
        self._internal_debugger.argv.prevent_empty = False

        # We must note inside the debugger if the path is different from the first argument in argv
        # This must be done last, otherwise we might get in an inconsistent state
        # if one of the previous checks fails
        self._internal_debugger._has_path_different_from_argv0 = True

    @property
    def kill_on_exit(self: Debugger) -> bool:
        """Get whether the process will be killed when the debugger exits."""
        return self._internal_debugger.kill_on_exit

    @kill_on_exit.setter
    def kill_on_exit(self: Debugger, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError("kill_on_exit must be a boolean")

        self._internal_debugger.kill_on_exit = value

    @property
    def threads(self: Debugger) -> list[ThreadContext]:
        """Get the list of threads in the process."""
        return self._internal_debugger.threads

    @property
    def breakpoints(self: Debugger) -> dict[int, Breakpoint]:
        """Get the breakpoints set on the process."""
        return self._internal_debugger.breakpoints

    @property
    def children(self: Debugger) -> list[Debugger]:
        """Get the list of child debuggers."""
        return self._internal_debugger.children

    @property
    def handled_syscalls(self: InternalDebugger) -> dict[int, SyscallHandler]:
        """Get the handled syscalls dictionary.

        Returns:
            dict[int, SyscallHandler]: the handled syscalls dictionary.
        """
        return self._internal_debugger.handled_syscalls

    @property
    def caught_signals(self: InternalDebugger) -> dict[int, SignalCatcher]:
        """Get the caught signals dictionary.

        Returns:
            dict[int, SignalCatcher]: the caught signals dictionary.
        """
        return self._internal_debugger.caught_signals

    @property
    def maps(self: Debugger) -> MemoryMapList[MemoryMap]:
        """Get the memory maps of the process."""
        return self._internal_debugger.maps

    @property
    def pprint_syscalls(self: Debugger) -> bool:
        """Get the state of the pprint_syscalls flag.

        Returns:
            bool: True if the debugger should pretty print syscalls, False otherwise.
        """
        return self._internal_debugger.pprint_syscalls

    @pprint_syscalls.setter
    def pprint_syscalls(self: Debugger, value: bool) -> None:
        """Set the state of the pprint_syscalls flag.

        Args:
            value (bool): the value to set.
        """
        if not isinstance(value, bool):
            raise TypeError("pprint_syscalls must be a boolean")
        if value:
            self._internal_debugger.enable_pretty_print()
        else:
            self._internal_debugger.disable_pretty_print()

        self._internal_debugger.pprint_syscalls = value

    @contextmanager
    def pprint_syscalls_context(self: Debugger, value: bool) -> ...:
        """A context manager to temporarily change the state of the pprint_syscalls flag.

        Args:
            value (bool): the value to set.
        """
        old_value = self.pprint_syscalls
        self.pprint_syscalls = value
        yield
        self.pprint_syscalls = old_value

    @property
    def syscalls_to_pprint(self: Debugger) -> list[str] | None:
        """Get the syscalls to pretty print.

        Returns:
            list[str]: The syscalls to pretty print.
        """
        if self._internal_debugger.syscalls_to_pprint is None:
            return None
        else:
            return [
                resolve_syscall_name(self._internal_debugger.arch, v)
                for v in self._internal_debugger.syscalls_to_pprint
            ]

    @syscalls_to_pprint.setter
    def syscalls_to_pprint(self: Debugger, value: list[int | str] | None) -> None:
        """Get the syscalls to pretty print.

        Args:
            value (list[int | str] | None): The syscalls to pretty print.
        """
        if value is None:
            self._internal_debugger.syscalls_to_pprint = None
        elif isinstance(value, list):
            self._internal_debugger.syscalls_to_pprint = [
                v if isinstance(v, int) else resolve_syscall_number(self._internal_debugger.arch, v) for v in value
            ]
        else:
            raise ValueError(
                "syscalls_to_pprint must be a list of integers or strings or None.",
            )
        if self._internal_debugger.pprint_syscalls:
            self._internal_debugger.enable_pretty_print()

    @property
    def syscalls_to_not_pprint(self: Debugger) -> list[str] | None:
        """Get the syscalls to not pretty print.

        Returns:
            list[str]: The syscalls to not pretty print.
        """
        if self._internal_debugger.syscalls_to_not_pprint is None:
            return None
        else:
            return [
                resolve_syscall_name(self._internal_debugger.arch, v)
                for v in self._internal_debugger.syscalls_to_not_pprint
            ]

    @syscalls_to_not_pprint.setter
    def syscalls_to_not_pprint(self: Debugger, value: list[int | str] | None) -> None:
        """Get the syscalls to not pretty print.

        Args:
            value (list[int | str] | None): The syscalls to not pretty print.
        """
        if value is None:
            self._internal_debugger.syscalls_to_not_pprint = None
        elif isinstance(value, list):
            self._internal_debugger.syscalls_to_not_pprint = [
                v if isinstance(v, int) else resolve_syscall_number(self._internal_debugger.arch, v) for v in value
            ]
        else:
            raise ValueError(
                "syscalls_to_not_pprint must be a list of integers or strings or None.",
            )
        if self._internal_debugger.pprint_syscalls:
            self._internal_debugger.enable_pretty_print()

    @property
    def signals_to_block(self: Debugger) -> list[str]:
        """Get the signals to not forward to the process.

        Returns:
            list[str]: The signals to block.
        """
        return [resolve_signal_name(v) for v in self._internal_debugger.signals_to_block]

    @signals_to_block.setter
    def signals_to_block(self: Debugger, signals: list[int | str]) -> None:
        """Set the signal to not forward to the process.

        Args:
            signals (list[int | str]): The signals to block.
        """
        if not isinstance(signals, list):
            raise TypeError("signals_to_block must be a list of integers or strings")

        signals = [v if isinstance(v, int) else resolve_signal_number(v) for v in signals]

        if not set(signals).issubset(get_all_signal_numbers()):
            raise ValueError("Invalid signal number.")

        self._internal_debugger.signals_to_block = signals

    @property
    def fast_memory(self: Debugger) -> bool:
        """Get the state of the fast_memory flag.

        It is used to determine if the debugger should use a faster memory access method.

        Returns:
            bool: True if the debugger should use a faster memory access method, False otherwise.
        """
        return self._internal_debugger.fast_memory

    @fast_memory.setter
    def fast_memory(self: Debugger, value: bool) -> None:
        """Set the state of the fast_memory flag.

        It is used to determine if the debugger should use a faster memory access method.

        Args:
            value (bool): the value to set.
        """
        if not isinstance(value, bool):
            raise TypeError("fast_memory must be a boolean")
        self._internal_debugger.fast_memory = value

    @property
    def instruction_pointer(self: Debugger) -> int:
        """Get the main thread's instruction pointer."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].instruction_pointer

    @instruction_pointer.setter
    def instruction_pointer(self: Debugger, value: int) -> None:
        """Set the main thread's instruction pointer."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].instruction_pointer = value

    @property
    def syscall_arg0(self: Debugger) -> int:
        """Get the main thread's syscall argument 0."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_arg0

    @syscall_arg0.setter
    def syscall_arg0(self: Debugger, value: int) -> None:
        """Set the main thread's syscall argument 0."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_arg0 = value

    @property
    def syscall_arg1(self: Debugger) -> int:
        """Get the main thread's syscall argument 1."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_arg1

    @syscall_arg1.setter
    def syscall_arg1(self: Debugger, value: int) -> None:
        """Set the main thread's syscall argument 1."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_arg1 = value

    @property
    def syscall_arg2(self: Debugger) -> int:
        """Get the main thread's syscall argument 2."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_arg2

    @syscall_arg2.setter
    def syscall_arg2(self: Debugger, value: int) -> None:
        """Set the main thread's syscall argument 2."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_arg2 = value

    @property
    def syscall_arg3(self: Debugger) -> int:
        """Get the main thread's syscall argument 3."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_arg3

    @syscall_arg3.setter
    def syscall_arg3(self: Debugger, value: int) -> None:
        """Set the main thread's syscall argument 3."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_arg3 = value

    @property
    def syscall_arg4(self: Debugger) -> int:
        """Get the main thread's syscall argument 4."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_arg4

    @syscall_arg4.setter
    def syscall_arg4(self: Debugger, value: int) -> None:
        """Set the main thread's syscall argument 4."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_arg4 = value

    @property
    def syscall_arg5(self: Debugger) -> int:
        """Get the main thread's syscall argument 5."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_arg5

    @syscall_arg5.setter
    def syscall_arg5(self: Debugger, value: int) -> None:
        """Set the main thread's syscall argument 5."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_arg5 = value

    @property
    def syscall_number(self: Debugger) -> int:
        """Get the main thread's syscall number."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_number

    @syscall_number.setter
    def syscall_number(self: Debugger, value: int) -> None:
        """Set the main thread's syscall number."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_number = value

    @property
    def syscall_return(self: Debugger) -> int:
        """Get the main thread's syscall return value."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].syscall_return

    @syscall_return.setter
    def syscall_return(self: Debugger, value: int) -> None:
        """Set the main thread's syscall return value."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].syscall_return = value

    @property
    def regs(self: Debugger) -> Registers:
        """Get the main thread's registers."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self._internal_debugger._ensure_process_stopped_regs()
        return self.threads[0].regs

    @property
    def dead(self: Debugger) -> bool:
        """Whether the process is dead."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].dead

    @property
    def zombie(self: Debugger) -> None:
        """Whether the main thread is a zombie."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].zombie

    @property
    def memory(self: Debugger) -> AbstractMemoryView:
        """The memory view of the process."""
        return self._internal_debugger.memory

    @property
    def mem(self: Debugger) -> AbstractMemoryView:
        """Alias for the `memory` property.

        Get the memory view of the process.
        """
        return self._internal_debugger.memory

    @property
    def process_id(self: Debugger) -> int:
        """The process ID."""
        return self._internal_debugger.process_id

    @property
    def pid(self: Debugger) -> int:
        """Alias for `process_id` property.

        The process ID.
        """
        return self._internal_debugger.process_id

    @property
    def thread_id(self: Debugger) -> int:
        """The thread ID of the main thread."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].tid

    @property
    def tid(self: Debugger) -> int:
        """Alias for `thread_id` property.

        The thread ID of the main thread.
        """
        return self._thread_id

    @property
    def running(self: Debugger) -> bool:
        """Whether the process is running."""
        return self._internal_debugger.running

    @property
    def saved_ip(self: Debugger) -> int:
        """Get the saved instruction pointer of the main thread."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].saved_ip

    @property
    def exit_code(self: Debugger) -> int | None:
        """The main thread's exit code."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].exit_code

    @property
    def exit_signal(self: Debugger) -> str | None:
        """The main thread's exit signal."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].exit_signal

    @property
    def signal(self: Debugger) -> str | None:
        """The signal to be forwarded to the main thread."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].signal

    @signal.setter
    def signal(self: Debugger, signal: str | int) -> None:
        """Set the signal to forward to the main thread."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].signal = signal

    @property
    def signal_number(self: Debugger) -> int | None:
        """The signal number to be forwarded to the main thread."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].signal_number

    def backtrace(self: Debugger, as_symbols: bool = False) -> list:
        """Returns the current backtrace of the main thread.

        Args:
            as_symbols (bool, optional): Whether to return the backtrace as symbols
        """
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        return self.threads[0].backtrace(as_symbols)

    def pprint_backtrace(self: Debugger) -> None:
        """Pretty pints the current backtrace of the main thread."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].pprint_backtrace()

    def pprint_registers(self: Debugger) -> None:
        """Pretty prints the main thread's registers."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].pprint_registers()

    def pprint_regs(self: Debugger) -> None:
        """Alias for the `pprint_registers` method.

        Pretty prints the main thread's registers.
        """
        self.pprint_registers()

    def pprint_registers_all(self: Debugger) -> None:
        """Pretty prints all the main thread's registers."""
        if not self.threads:
            raise RuntimeError("No threads available. Did you call `run` or `attach`?")
        self.threads[0].pprint_registers_all()

    def pprint_regs_all(self: Debugger) -> None:
        """Alias for the `pprint_registers_all` method.

        Pretty prints all the main thread's registers.
        """
        self.pprint_registers_all()

    def pprint_memory(
        self: Debugger,
        start: int,
        end: int,
        file: str = "hybrid",
        override_word_size: int | None = None,
        integer_mode: bool = False,
    ) -> None:
        """Pretty prints the memory contents of the process.

        Args:
            start (int): The start address of the memory region.
            end (int): The end address of the memory region.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
            override_word_size (int, optional): The word size to use for the memory dump. Defaults to None.
            integer_mode (bool, optional): Whether to print the memory contents as integers. Defaults to False.
        """
        self._internal_debugger.pprint_memory(start, end, file, override_word_size, integer_mode)

    def step(self: Debugger) -> None:
        """Executes a single instruction of the process."""
        self._internal_debugger.step(self)

    def step_until(
        self: Debugger,
        position: int | str,
        max_steps: int = -1,
        file: str = "hybrid",
    ) -> None:
        """Executes instructions of the process until the specified location is reached.

        Args:
            position (int | bytes): The location to reach.
            max_steps (int, optional): The maximum number of steps to execute. Defaults to -1.
            file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        """
        self._internal_debugger.step_until(self, position, max_steps, file)

    def finish(self: Debugger, heuristic: str = "backtrace") -> None:
        """Continues execution until the current function returns or the process stops.

        The command requires a heuristic to determine the end of the function. The available heuristics are:
        - `backtrace`: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads.
        - `step-mode`: The debugger will step on the specified thread until the current function returns. This will be slower.

        Args:
            heuristic (str, optional): The heuristic to use. Defaults to "backtrace".
        """
        self._internal_debugger.finish(self, heuristic=heuristic)

    def next(self: Debugger) -> None:
        """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns."""
        self._internal_debugger.next(self)

    def si(self: Debugger) -> None:
        """Alias for the `step` method.

        Executes a single instruction of the process.
        """
        self._internal_debugger.step(self)

    def su(
        self: Debugger,
        position: int | str,
        max_steps: int = -1,
    ) -> None:
        """Alias for the `step_until` method.

        Executes instructions of the process until the specified location is reached.

        Args:
            position (int | bytes): The location to reach.
            max_steps (int, optional): The maximum number of steps to execute. Defaults to -1.
        """
        self._internal_debugger.step_until(self, position, max_steps)

    def fin(self: Debugger, heuristic: str = "backtrace") -> None:
        """Alias for the `finish` method. Continues execution until the current function returns or the process stops.

        The command requires a heuristic to determine the end of the function. The available heuristics are:
        - `backtrace`: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads.
        - `step-mode`: The debugger will step on the specified thread until the current function returns. This will be slower.

        Args:
            heuristic (str, optional): The heuristic to use. Defaults to "backtrace".
        """
        self._internal_debugger.finish(self, heuristic)

    def ni(self: Debugger) -> None:
        """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns."""
        self._internal_debugger.next(self)

    def __repr__(self: Debugger) -> str:
        """Return the string representation of the `Debugger` object."""
        repr_str = "Debugger("
        repr_str += f"argv = {self._internal_debugger.argv}, "
        repr_str += f"path = {self._internal_debugger.path}, "
        repr_str += f"aslr = {self._internal_debugger.aslr_enabled}, "
        repr_str += f"env = {self._internal_debugger.env}, "
        repr_str += f"escape_antidebug = {self._internal_debugger.escape_antidebug}, "
        repr_str += f"continue_to_binary_entrypoint = {self._internal_debugger.autoreach_entrypoint}, "
        repr_str += f"auto_interrupt_on_command = {self._internal_debugger.auto_interrupt_on_command}, "
        repr_str += f"fast_memory = {self._internal_debugger.fast_memory}, "
        repr_str += f"kill_on_exit = {self._internal_debugger.kill_on_exit})\n"
        repr_str += f"follow_children = {self._internal_debugger.follow_children}, "
        repr_str += f"  Architecture: {self.arch}\n"
        repr_str += "  Threads:"
        for thread in self.threads:
            repr_str += f"\n    ({thread.tid}, {'dead' if thread.dead else 'alive'}) "
            repr_str += f"ip: {thread.instruction_pointer:#x}"
        return repr_str

    def create_snapshot(self: Debugger, level: str = "base", name: str | None = None) -> ProcessSnapshot:
        """Create a snapshot of the current process state.

        Snapshot levels:
        - base: Registers
        - writable: Registers, writable memory contents
        - full: Registers, all memory contents

        Args:
            level (str): The level of the snapshot.
            name (str, optional): The name of the snapshot. Defaults to None.

        Returns:
            ProcessSnapshot: The created snapshot.
        """
        return self._internal_debugger.create_snapshot(level, name)

    def load_snapshot(self: Debugger, file_path: str) -> Snapshot:
        """Load a snapshot of the thread / process state.

        Args:
            file_path (str): The path to the snapshot file.
        """
        return self._internal_debugger.load_snapshot(file_path)

_internal_debugger instance-attribute

The internal debugger object.

_previous_argv instance-attribute

A copy of the previous argv state, used internally to detect changes to argv[0].

_sentinel = object() class-attribute instance-attribute

A sentinel object.

arch property writable

Get the architecture of the process.

argv property writable

The command line arguments of the debugged process.

breakpoints property

Get the breakpoints set on the process.

caught_signals property

Get the caught signals dictionary.

Returns:

Type Description
dict[int, SignalCatcher]

dict[int, SignalCatcher]: the caught signals dictionary.

children property

Get the list of child debuggers.

dead property

Whether the process is dead.

env property writable

The environment variables of the debugged process.

exit_code property

The main thread's exit code.

exit_signal property

The main thread's exit signal.

fast_memory property writable

Get the state of the fast_memory flag.

It is used to determine if the debugger should use a faster memory access method.

Returns:

Name Type Description
bool bool

True if the debugger should use a faster memory access method, False otherwise.

handled_syscalls property

Get the handled syscalls dictionary.

Returns:

Type Description
dict[int, SyscallHandler]

dict[int, SyscallHandler]: the handled syscalls dictionary.

instruction_pointer property writable

Get the main thread's instruction pointer.

is_in_gdb property

Returns whether the process is in GDB.

kill_on_exit property writable

Get whether the process will be killed when the debugger exits.

maps property

Get the memory maps of the process.

mem property

Alias for the memory property.

Get the memory view of the process.

memory property

The memory view of the process.

path property writable

The resolved path to the debugged binary.

pid property

Alias for process_id property.

The process ID.

pprint_syscalls property writable

Get the state of the pprint_syscalls flag.

Returns:

Name Type Description
bool bool

True if the debugger should pretty print syscalls, False otherwise.

process_id property

The process ID.

regs property

Get the main thread's registers.

running property

Whether the process is running.

saved_ip property

Get the saved instruction pointer of the main thread.

signal property writable

The signal to be forwarded to the main thread.

signal_number property

The signal number to be forwarded to the main thread.

signals_to_block property writable

Get the signals to not forward to the process.

Returns:

Type Description
list[str]

list[str]: The signals to block.

symbols property

Get the symbols of the process.

syscall_arg0 property writable

Get the main thread's syscall argument 0.

syscall_arg1 property writable

Get the main thread's syscall argument 1.

syscall_arg2 property writable

Get the main thread's syscall argument 2.

syscall_arg3 property writable

Get the main thread's syscall argument 3.

syscall_arg4 property writable

Get the main thread's syscall argument 4.

syscall_arg5 property writable

Get the main thread's syscall argument 5.

syscall_number property writable

Get the main thread's syscall number.

syscall_return property writable

Get the main thread's syscall return value.

syscalls_to_not_pprint property writable

Get the syscalls to not pretty print.

Returns:

Type Description
list[str] | None

list[str]: The syscalls to not pretty print.

syscalls_to_pprint property writable

Get the syscalls to pretty print.

Returns:

Type Description
list[str] | None

list[str]: The syscalls to pretty print.

thread_id property

The thread ID of the main thread.

threads property

Get the list of threads in the process.

tid property

Alias for thread_id property.

The thread ID of the main thread.

zombie property

Whether the main thread is a zombie.

__repr__()

Return the string representation of the Debugger object.

Source code in libdebug/debugger/debugger.py
def __repr__(self: Debugger) -> str:
    """Return the string representation of the `Debugger` object."""
    repr_str = "Debugger("
    repr_str += f"argv = {self._internal_debugger.argv}, "
    repr_str += f"path = {self._internal_debugger.path}, "
    repr_str += f"aslr = {self._internal_debugger.aslr_enabled}, "
    repr_str += f"env = {self._internal_debugger.env}, "
    repr_str += f"escape_antidebug = {self._internal_debugger.escape_antidebug}, "
    repr_str += f"continue_to_binary_entrypoint = {self._internal_debugger.autoreach_entrypoint}, "
    repr_str += f"auto_interrupt_on_command = {self._internal_debugger.auto_interrupt_on_command}, "
    repr_str += f"fast_memory = {self._internal_debugger.fast_memory}, "
    repr_str += f"kill_on_exit = {self._internal_debugger.kill_on_exit})\n"
    repr_str += f"follow_children = {self._internal_debugger.follow_children}, "
    repr_str += f"  Architecture: {self.arch}\n"
    repr_str += "  Threads:"
    for thread in self.threads:
        repr_str += f"\n    ({thread.tid}, {'dead' if thread.dead else 'alive'}) "
        repr_str += f"ip: {thread.instruction_pointer:#x}"
    return repr_str

_configure_argument_list(argv)

Sets up the ArgumentList with the before/after callbacks, and freezes argv[0] if needed.

Source code in libdebug/debugger/debugger.py
def _configure_argument_list(self: Debugger, argv: ArgumentList) -> None:
    """Sets up the ArgumentList with the before/after callbacks, and freezes argv[0] if needed."""
    # If the user has not specified a different path, and argv is not empty, we should freeze argv[0]
    if not self._internal_debugger._has_path_different_from_argv0 and argv and argv[0]:
        argv.prevent_empty = True
    else:
        argv.prevent_empty = False

    # We register a _before_callback that stores a copy of the current argv state
    def _before_callback(_: list[str]) -> None:
        """Store a copy of the current argv state."""
        # Changing argv is not allowed while the process is being debugged.
        if self._internal_debugger.is_debugging:
            raise RuntimeError("Cannot change argv while the process is running. Please kill it first.")

        self._previous_argv = list(self._internal_debugger.argv) if self._internal_debugger.argv else []

    # The _after callback should check if argv[0] has changed and update the path accordingly
    def _after_callback(new_argv: list[str]) -> None:
        """An after callback that updates the path if argv[0] has changed."""
        if not hasattr(self, "_previous_argv"):
            raise RuntimeError("The _previous_argv attribute is not set. This should not happen.")

        try:
            if (
                not self._internal_debugger._has_path_different_from_argv0
                and new_argv
                and new_argv[0] != self._previous_argv[0]
            ):
                self._internal_debugger.clear_all_caches()
                # Changing path can also change the architecture, so we need to update it
                resolved_path = resolve_argv_path(new_argv[0])
                self.arch = elf_architecture(resolved_path)
                self._internal_debugger.path = resolved_path
        except Exception:
            # We revert to the previous argv state if something goes wrong
            self._internal_debugger.argv = ArgumentList(self._previous_argv)
            raise

    # Set the callbacks on the ArgumentList
    argv.set_callbacks(_before_callback, _after_callback)

_configure_env_dict()

Sets up the EnvDict with the before callback.

Source code in libdebug/debugger/debugger.py
def _configure_env_dict(self: Debugger) -> None:
    """Sets up the EnvDict with the before callback."""
    # We register a _before_callback that ensure that the process
    # is not being debugged when the environment is changed
    def _before_callback() -> None:
        """Ensure that the process is not being debugged when the environment is changed."""
        # Changing env is not allowed while the process is being debugged.
        if self._internal_debugger.is_debugging:
            raise RuntimeError("Cannot change env while the process is running. Please kill it first.")

    if self._internal_debugger.env is not None:
        # If the env is already set, we just need to set the callback
        self._internal_debugger.env.set_callback(_before_callback)

attach(pid)

Attaches to an existing process.

Source code in libdebug/debugger/debugger.py
def attach(self: Debugger, pid: int) -> None:
    """Attaches to an existing process."""
    self._internal_debugger.attach(pid)

backtrace(as_symbols=False)

Returns the current backtrace of the main thread.

Parameters:

Name Type Description Default
as_symbols bool

Whether to return the backtrace as symbols

False
Source code in libdebug/debugger/debugger.py
def backtrace(self: Debugger, as_symbols: bool = False) -> list:
    """Returns the current backtrace of the main thread.

    Args:
        as_symbols (bool, optional): Whether to return the backtrace as symbols
    """
    if not self.threads:
        raise RuntimeError("No threads available. Did you call `run` or `attach`?")
    return self.threads[0].backtrace(as_symbols)

bp(position, hardware=False, condition='x', length=1, callback=None, file='hybrid')

Alias for the breakpoint method.

Parameters:

Name Type Description Default
position int | bytes

The location of the breakpoint.

required
hardware bool

Whether the breakpoint should be hardware-assisted or purely software. Defaults to False.

False
condition str

The trigger condition for the breakpoint. Defaults to None.

'x'
length int

The length of the breakpoint. Only for watchpoints. Defaults to 1.

1
callback Callable[[ThreadContext, Breakpoint], None]

A callback to be called when the breakpoint is hit. Defaults to None.

None
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/debugger/debugger.py
def bp(
    self: Debugger,
    position: int | str,
    hardware: bool = False,
    condition: str = "x",
    length: int = 1,
    callback: None | Callable[[ThreadContext, Breakpoint], None] = None,
    file: str = "hybrid",
) -> Breakpoint:
    """Alias for the `breakpoint` method.

    Args:
        position (int | bytes): The location of the breakpoint.
        hardware (bool, optional): Whether the breakpoint should be hardware-assisted or purely software. Defaults to False.
        condition (str, optional): The trigger condition for the breakpoint. Defaults to None.
        length (int, optional): The length of the breakpoint. Only for watchpoints. Defaults to 1.
        callback (Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the breakpoint is hit. Defaults to None.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    return self._internal_debugger.breakpoint(position, hardware, condition, length, callback, file)

breakpoint(position, hardware=False, condition='x', length=1, callback=None, file='hybrid')

Sets a breakpoint at the specified location.

Parameters:

Name Type Description Default
position int | bytes

The location of the breakpoint.

required
hardware bool

Whether the breakpoint should be hardware-assisted or purely software. Defaults to False.

False
condition str

The trigger condition for the breakpoint. Defaults to None.

'x'
length int

The length of the breakpoint. Only for watchpoints. Defaults to 1.

1
callback None | bool | Callable[[ThreadContext, Breakpoint], None]

A callback to be called when the breakpoint is hit. If True, an empty callback will be set. Defaults to None.

None
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/debugger/debugger.py
def breakpoint(
    self: Debugger,
    position: int | str,
    hardware: bool = False,
    condition: str = "x",
    length: int = 1,
    callback: None | bool | Callable[[ThreadContext, Breakpoint], None] = None,
    file: str = "hybrid",
) -> Breakpoint:
    """Sets a breakpoint at the specified location.

    Args:
        position (int | bytes): The location of the breakpoint.
        hardware (bool, optional): Whether the breakpoint should be hardware-assisted or purely software. Defaults to False.
        condition (str, optional): The trigger condition for the breakpoint. Defaults to None.
        length (int, optional): The length of the breakpoint. Only for watchpoints. Defaults to 1.
        callback (None | bool | Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the breakpoint is hit. If True, an empty callback will be set. Defaults to None.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    return self._internal_debugger.breakpoint(position, hardware, condition, length, callback, file)

c()

Alias for the cont method.

Continues the process.

Source code in libdebug/debugger/debugger.py
def c(self: Debugger) -> None:
    """Alias for the `cont` method.

    Continues the process.
    """
    self._internal_debugger.cont()

catch_signal(signal, callback=None, recursive=False)

Catch a signal in the target process.

Parameters:

Name Type Description Default
signal int | str

The signal to catch. If "*", "ALL", "all" or -1 is passed, all signals will be caught.

required
callback None | bool | Callable[[ThreadContext, SignalCatcher], None]

A callback to be called when the signal is caught. If True, an empty callback will be set. Defaults to None.

None
recursive bool

Whether, when the signal is hijacked with another one, the signal catcher associated with the new signal should be considered as well. Defaults to False.

False

Returns:

Name Type Description
SignalCatcher SignalCatcher

The SignalCatcher object.

Source code in libdebug/debugger/debugger.py
def catch_signal(
    self: Debugger,
    signal: int | str,
    callback: None | bool | Callable[[ThreadContext, SignalCatcher], None] = None,
    recursive: bool = False,
) -> SignalCatcher:
    """Catch a signal in the target process.

    Args:
        signal (int | str): The signal to catch. If "*", "ALL", "all" or -1 is passed, all signals will be caught.
        callback (None | bool | Callable[[ThreadContext, SignalCatcher], None], optional): A callback to be called when the signal is caught. If True, an empty callback will be set. Defaults to None.
        recursive (bool, optional): Whether, when the signal is hijacked with another one, the signal catcher associated with the new signal should be considered as well. Defaults to False.

    Returns:
        SignalCatcher: The SignalCatcher object.
    """
    return self._internal_debugger.catch_signal(signal, callback, recursive)

cont()

Continues the process.

Source code in libdebug/debugger/debugger.py
def cont(self: Debugger) -> None:
    """Continues the process."""
    self._internal_debugger.cont()

create_snapshot(level='base', name=None)

Create a snapshot of the current process state.

Snapshot levels: - base: Registers - writable: Registers, writable memory contents - full: Registers, all memory contents

Parameters:

Name Type Description Default
level str

The level of the snapshot.

'base'
name str

The name of the snapshot. Defaults to None.

None

Returns:

Name Type Description
ProcessSnapshot ProcessSnapshot

The created snapshot.

Source code in libdebug/debugger/debugger.py
def create_snapshot(self: Debugger, level: str = "base", name: str | None = None) -> ProcessSnapshot:
    """Create a snapshot of the current process state.

    Snapshot levels:
    - base: Registers
    - writable: Registers, writable memory contents
    - full: Registers, all memory contents

    Args:
        level (str): The level of the snapshot.
        name (str, optional): The name of the snapshot. Defaults to None.

    Returns:
        ProcessSnapshot: The created snapshot.
    """
    return self._internal_debugger.create_snapshot(level, name)

detach()

Detaches from the process.

Source code in libdebug/debugger/debugger.py
def detach(self: Debugger) -> None:
    """Detaches from the process."""
    self._internal_debugger.detach()

fin(heuristic='backtrace')

Alias for the finish method. Continues execution until the current function returns or the process stops.

The command requires a heuristic to determine the end of the function. The available heuristics are: - backtrace: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads. - step-mode: The debugger will step on the specified thread until the current function returns. This will be slower.

Parameters:

Name Type Description Default
heuristic str

The heuristic to use. Defaults to "backtrace".

'backtrace'
Source code in libdebug/debugger/debugger.py
def fin(self: Debugger, heuristic: str = "backtrace") -> None:
    """Alias for the `finish` method. Continues execution until the current function returns or the process stops.

    The command requires a heuristic to determine the end of the function. The available heuristics are:
    - `backtrace`: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads.
    - `step-mode`: The debugger will step on the specified thread until the current function returns. This will be slower.

    Args:
        heuristic (str, optional): The heuristic to use. Defaults to "backtrace".
    """
    self._internal_debugger.finish(self, heuristic)

finish(heuristic='backtrace')

Continues execution until the current function returns or the process stops.

The command requires a heuristic to determine the end of the function. The available heuristics are: - backtrace: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads. - step-mode: The debugger will step on the specified thread until the current function returns. This will be slower.

Parameters:

Name Type Description Default
heuristic str

The heuristic to use. Defaults to "backtrace".

'backtrace'
Source code in libdebug/debugger/debugger.py
def finish(self: Debugger, heuristic: str = "backtrace") -> None:
    """Continues execution until the current function returns or the process stops.

    The command requires a heuristic to determine the end of the function. The available heuristics are:
    - `backtrace`: The debugger will place a breakpoint on the saved return address found on the stack and continue execution on all threads.
    - `step-mode`: The debugger will step on the specified thread until the current function returns. This will be slower.

    Args:
        heuristic (str, optional): The heuristic to use. Defaults to "backtrace".
    """
    self._internal_debugger.finish(self, heuristic=heuristic)

gdb(migrate_breakpoints=True, open_in_new_process=True, blocking=True)

Migrates the current debugging session to GDB.

Parameters:

Name Type Description Default
migrate_breakpoints bool

Whether to migrate over the breakpoints set in libdebug to GDB.

True
open_in_new_process bool

Whether to attempt to open GDB in a new process instead of the current one.

True
blocking bool

Whether to block the script until GDB is closed.

True
Source code in libdebug/debugger/debugger.py
def gdb(
    self: Debugger,
    migrate_breakpoints: bool = True,
    open_in_new_process: bool = True,
    blocking: bool = True,
) -> GdbResumeEvent:
    """Migrates the current debugging session to GDB.

    Args:
        migrate_breakpoints (bool): Whether to migrate over the breakpoints set in libdebug to GDB.
        open_in_new_process (bool): Whether to attempt to open GDB in a new process instead of the current one.
        blocking (bool): Whether to block the script until GDB is closed.
    """
    return self._internal_debugger.gdb(migrate_breakpoints, open_in_new_process, blocking)

handle_syscall(syscall, on_enter=None, on_exit=None, recursive=False)

Handle a syscall in the target process.

Parameters:

Name Type Description Default
syscall int | str

The syscall name or number to handle. If "*", "ALL", "all" or -1 is passed, all syscalls will be handled.

required
on_enter None | bool | Callable[[ThreadContext, SyscallHandler], None]

The callback to execute when the syscall is entered. If True, an empty callback will be set. Defaults to None.

None
on_exit None | bool | Callable[[ThreadContext, SyscallHandler], None]

The callback to execute when the syscall is exited. If True, an empty callback will be set. Defaults to None.

None
recursive bool

Whether, when the syscall is hijacked with another one, the syscall handler associated with the new syscall should be considered as well. Defaults to False.

False

Returns:

Name Type Description
SyscallHandler SyscallHandler

The SyscallHandler object.

Source code in libdebug/debugger/debugger.py
def handle_syscall(
    self: Debugger,
    syscall: int | str,
    on_enter: None | bool | Callable[[ThreadContext, SyscallHandler], None] = None,
    on_exit: None | bool | Callable[[ThreadContext, SyscallHandler], None] = None,
    recursive: bool = False,
) -> SyscallHandler:
    """Handle a syscall in the target process.

    Args:
        syscall (int | str): The syscall name or number to handle. If "*", "ALL", "all" or -1 is passed, all syscalls will be handled.
        on_enter (None | bool |Callable[[ThreadContext, SyscallHandler], None], optional): The callback to execute when the syscall is entered. If True, an empty callback will be set. Defaults to None.
        on_exit (None | bool | Callable[[ThreadContext, SyscallHandler], None], optional): The callback to execute when the syscall is exited. If True, an empty callback will be set. Defaults to None.
        recursive (bool, optional): Whether, when the syscall is hijacked with another one, the syscall handler associated with the new syscall should be considered as well. Defaults to False.

    Returns:
        SyscallHandler: The SyscallHandler object.
    """
    return self._internal_debugger.handle_syscall(syscall, on_enter, on_exit, recursive)

hijack_signal(original_signal, new_signal, recursive=False)

Hijack a signal in the target process.

Parameters:

Name Type Description Default
original_signal int | str

The signal to hijack. If "*", "ALL", "all" or -1 is passed, all signals will be hijacked.

required
new_signal int | str

The signal to hijack the original signal with.

required
recursive bool

Whether, when the signal is hijacked with another one, the signal catcher associated with the new signal should be considered as well. Defaults to False.

False

Returns:

Name Type Description
SignalCatcher SyscallHandler

The SignalCatcher object.

Source code in libdebug/debugger/debugger.py
def hijack_signal(
    self: Debugger,
    original_signal: int | str,
    new_signal: int | str,
    recursive: bool = False,
) -> SyscallHandler:
    """Hijack a signal in the target process.

    Args:
        original_signal (int | str): The signal to hijack. If "*", "ALL", "all" or -1 is passed, all signals will be hijacked.
        new_signal (int | str): The signal to hijack the original signal with.
        recursive (bool, optional): Whether, when the signal is hijacked with another one, the signal catcher associated with the new signal should be considered as well. Defaults to False.

    Returns:
        SignalCatcher: The SignalCatcher object.
    """
    return self._internal_debugger.hijack_signal(original_signal, new_signal, recursive)

hijack_syscall(original_syscall, new_syscall, recursive=False, **kwargs)

Hijacks a syscall in the target process.

Parameters:

Name Type Description Default
original_syscall int | str

The syscall name or number to hijack. If "*", "ALL", "all" or -1 is passed, all syscalls will be hijacked.

required
new_syscall int | str

The syscall name or number to hijack the original syscall with.

required
recursive bool

Whether, when the syscall is hijacked with another one, the syscall handler associated with the new syscall should be considered as well. Defaults to False.

False
**kwargs int

(int, optional): The arguments to pass to the new syscall.

{}

Returns:

Name Type Description
SyscallHandler SyscallHandler

The SyscallHandler object.

Source code in libdebug/debugger/debugger.py
def hijack_syscall(
    self: Debugger,
    original_syscall: int | str,
    new_syscall: int | str,
    recursive: bool = False,
    **kwargs: int,
) -> SyscallHandler:
    """Hijacks a syscall in the target process.

    Args:
        original_syscall (int | str): The syscall name or number to hijack. If "*", "ALL", "all" or -1 is passed, all syscalls will be hijacked.
        new_syscall (int | str): The syscall name or number to hijack the original syscall with.
        recursive (bool, optional): Whether, when the syscall is hijacked with another one, the syscall handler associated with the new syscall should be considered as well. Defaults to False.
        **kwargs: (int, optional): The arguments to pass to the new syscall.

    Returns:
        SyscallHandler: The SyscallHandler object.
    """
    return self._internal_debugger.hijack_syscall(original_syscall, new_syscall, recursive, **kwargs)

int()

Alias for the interrupt method.

Interrupts the process.

Source code in libdebug/debugger/debugger.py
def int(self: Debugger) -> None:
    """Alias for the `interrupt` method.

    Interrupts the process.
    """
    self._internal_debugger.interrupt()

interrupt()

Interrupts the process.

Source code in libdebug/debugger/debugger.py
def interrupt(self: Debugger) -> None:
    """Interrupts the process."""
    self._internal_debugger.interrupt()

kill()

Kills the process.

Source code in libdebug/debugger/debugger.py
def kill(self: Debugger) -> None:
    """Kills the process."""
    self._internal_debugger.kill()

load_snapshot(file_path)

Load a snapshot of the thread / process state.

Parameters:

Name Type Description Default
file_path str

The path to the snapshot file.

required
Source code in libdebug/debugger/debugger.py
def load_snapshot(self: Debugger, file_path: str) -> Snapshot:
    """Load a snapshot of the thread / process state.

    Args:
        file_path (str): The path to the snapshot file.
    """
    return self._internal_debugger.load_snapshot(file_path)

next()

Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns.

Source code in libdebug/debugger/debugger.py
def next(self: Debugger) -> None:
    """Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns."""
    self._internal_debugger.next(self)

ni()

Alias for the next method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns.

Source code in libdebug/debugger/debugger.py
def ni(self: Debugger) -> None:
    """Alias for the `next` method. Executes the next instruction of the process. If the instruction is a call, the debugger will continue until the called function returns."""
    self._internal_debugger.next(self)

post_init_(internal_debugger)

Do not use this constructor directly. Use the debugger function instead.

Source code in libdebug/debugger/debugger.py
def post_init_(self: Debugger, internal_debugger: InternalDebugger) -> None:
    """Do not use this constructor directly. Use the `debugger` function instead."""
    self._internal_debugger = internal_debugger
    self._internal_debugger.start_up()

    # We need to install the proper callbacks on the ArgumentList
    self._configure_argument_list(self._internal_debugger.argv)
    self._configure_env_dict()

pprint_backtrace()

Pretty pints the current backtrace of the main thread.

Source code in libdebug/debugger/debugger.py
def pprint_backtrace(self: Debugger) -> None:
    """Pretty pints the current backtrace of the main thread."""
    if not self.threads:
        raise RuntimeError("No threads available. Did you call `run` or `attach`?")
    self.threads[0].pprint_backtrace()

pprint_maps()

Prints the memory maps of the process.

Source code in libdebug/debugger/debugger.py
def pprint_maps(self: Debugger) -> None:
    """Prints the memory maps of the process."""
    self._internal_debugger.pprint_maps()

pprint_memory(start, end, file='hybrid', override_word_size=None, integer_mode=False)

Pretty prints the memory contents of the process.

Parameters:

Name Type Description Default
start int

The start address of the memory region.

required
end int

The end address of the memory region.

required
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
override_word_size int

The word size to use for the memory dump. Defaults to None.

None
integer_mode bool

Whether to print the memory contents as integers. Defaults to False.

False
Source code in libdebug/debugger/debugger.py
def pprint_memory(
    self: Debugger,
    start: int,
    end: int,
    file: str = "hybrid",
    override_word_size: int | None = None,
    integer_mode: bool = False,
) -> None:
    """Pretty prints the memory contents of the process.

    Args:
        start (int): The start address of the memory region.
        end (int): The end address of the memory region.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
        override_word_size (int, optional): The word size to use for the memory dump. Defaults to None.
        integer_mode (bool, optional): Whether to print the memory contents as integers. Defaults to False.
    """
    self._internal_debugger.pprint_memory(start, end, file, override_word_size, integer_mode)

pprint_registers()

Pretty prints the main thread's registers.

Source code in libdebug/debugger/debugger.py
def pprint_registers(self: Debugger) -> None:
    """Pretty prints the main thread's registers."""
    if not self.threads:
        raise RuntimeError("No threads available. Did you call `run` or `attach`?")
    self.threads[0].pprint_registers()

pprint_registers_all()

Pretty prints all the main thread's registers.

Source code in libdebug/debugger/debugger.py
def pprint_registers_all(self: Debugger) -> None:
    """Pretty prints all the main thread's registers."""
    if not self.threads:
        raise RuntimeError("No threads available. Did you call `run` or `attach`?")
    self.threads[0].pprint_registers_all()

pprint_regs()

Alias for the pprint_registers method.

Pretty prints the main thread's registers.

Source code in libdebug/debugger/debugger.py
def pprint_regs(self: Debugger) -> None:
    """Alias for the `pprint_registers` method.

    Pretty prints the main thread's registers.
    """
    self.pprint_registers()

pprint_regs_all()

Alias for the pprint_registers_all method.

Pretty prints all the main thread's registers.

Source code in libdebug/debugger/debugger.py
def pprint_regs_all(self: Debugger) -> None:
    """Alias for the `pprint_registers_all` method.

    Pretty prints all the main thread's registers.
    """
    self.pprint_registers_all()

pprint_syscalls_context(value)

A context manager to temporarily change the state of the pprint_syscalls flag.

Parameters:

Name Type Description Default
value bool

the value to set.

required
Source code in libdebug/debugger/debugger.py
@contextmanager
def pprint_syscalls_context(self: Debugger, value: bool) -> ...:
    """A context manager to temporarily change the state of the pprint_syscalls flag.

    Args:
        value (bool): the value to set.
    """
    old_value = self.pprint_syscalls
    self.pprint_syscalls = value
    yield
    self.pprint_syscalls = old_value

print_maps()

Prints the memory maps of the process.

Source code in libdebug/debugger/debugger.py
def print_maps(self: Debugger) -> None:
    """Prints the memory maps of the process."""
    liblog.warning("The `print_maps` method is deprecated. Use `d.pprint_maps` instead.")
    self._internal_debugger.pprint_maps()

r(redirect_pipes=True)

Alias for the run method.

Starts the process and waits for it to stop.

Parameters:

Name Type Description Default
redirect_pipes bool

Whether to hook and redirect the pipes of the process to a PipeManager.

True
Source code in libdebug/debugger/debugger.py
def r(self: Debugger, redirect_pipes: bool = True) -> PipeManager | None:
    """Alias for the `run` method.

    Starts the process and waits for it to stop.

    Args:
        redirect_pipes (bool): Whether to hook and redirect the pipes of the process to a PipeManager.
    """
    return self._internal_debugger.run(redirect_pipes)

resolve_symbol(symbol, file='binary')

Resolves the address of the specified symbol.

Parameters:

Name Type Description Default
symbol str

The symbol to resolve.

required
file str

The backing file to resolve the symbol in. Defaults to "binary"

'binary'

Returns:

Name Type Description
int int

The address of the symbol.

Source code in libdebug/debugger/debugger.py
def resolve_symbol(self: Debugger, symbol: str, file: str = "binary") -> int:
    """Resolves the address of the specified symbol.

    Args:
        symbol (str): The symbol to resolve.
        file (str): The backing file to resolve the symbol in. Defaults to "binary"

    Returns:
        int: The address of the symbol.
    """
    return self._internal_debugger.resolve_symbol(symbol, file)

run(timeout=-1, redirect_pipes=True)

Starts the process and waits for it to stop.

Parameters:

Name Type Description Default
timeout float

The timeout for the process to run. If -1, the process will run indefinitely.

-1
redirect_pipes bool

Whether to hook and redirect the pipes of the process to a PipeManager.

True
Source code in libdebug/debugger/debugger.py
def run(self: Debugger, timeout: float = -1, redirect_pipes: bool = True) -> PipeManager | None:
    """Starts the process and waits for it to stop.

    Args:
        timeout (float): The timeout for the process to run. If -1, the process will run indefinitely.
        redirect_pipes (bool): Whether to hook and redirect the pipes of the process to a PipeManager.
    """
    return self._internal_debugger.run(timeout, redirect_pipes)

si()

Alias for the step method.

Executes a single instruction of the process.

Source code in libdebug/debugger/debugger.py
def si(self: Debugger) -> None:
    """Alias for the `step` method.

    Executes a single instruction of the process.
    """
    self._internal_debugger.step(self)

step()

Executes a single instruction of the process.

Source code in libdebug/debugger/debugger.py
def step(self: Debugger) -> None:
    """Executes a single instruction of the process."""
    self._internal_debugger.step(self)

step_until(position, max_steps=-1, file='hybrid')

Executes instructions of the process until the specified location is reached.

Parameters:

Name Type Description Default
position int | bytes

The location to reach.

required
max_steps int

The maximum number of steps to execute. Defaults to -1.

-1
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/debugger/debugger.py
def step_until(
    self: Debugger,
    position: int | str,
    max_steps: int = -1,
    file: str = "hybrid",
) -> None:
    """Executes instructions of the process until the specified location is reached.

    Args:
        position (int | bytes): The location to reach.
        max_steps (int, optional): The maximum number of steps to execute. Defaults to -1.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    self._internal_debugger.step_until(self, position, max_steps, file)

su(position, max_steps=-1)

Alias for the step_until method.

Executes instructions of the process until the specified location is reached.

Parameters:

Name Type Description Default
position int | bytes

The location to reach.

required
max_steps int

The maximum number of steps to execute. Defaults to -1.

-1
Source code in libdebug/debugger/debugger.py
def su(
    self: Debugger,
    position: int | str,
    max_steps: int = -1,
) -> None:
    """Alias for the `step_until` method.

    Executes instructions of the process until the specified location is reached.

    Args:
        position (int | bytes): The location to reach.
        max_steps (int, optional): The maximum number of steps to execute. Defaults to -1.
    """
    self._internal_debugger.step_until(self, position, max_steps)

terminate()

Interrupts the process, kills it and then terminates the background thread.

The debugger object will not be usable after this method is called. This method should only be called to free up resources when the debugger object is no longer needed.

Source code in libdebug/debugger/debugger.py
def terminate(self: Debugger) -> None:
    """Interrupts the process, kills it and then terminates the background thread.

    The debugger object will not be usable after this method is called.
    This method should only be called to free up resources when the debugger object is no longer needed.
    """
    self._internal_debugger.terminate()

w()

Alias for the wait method.

Waits for the process to stop.

Source code in libdebug/debugger/debugger.py
def w(self: Debugger) -> None:
    """Alias for the `wait` method.

    Waits for the process to stop.
    """
    self._internal_debugger.wait()

wait()

Waits for the process to stop.

Source code in libdebug/debugger/debugger.py
def wait(self: Debugger) -> None:
    """Waits for the process to stop."""
    self._internal_debugger.wait()

wait_for_gdb()

Waits for the GDB process to migrate back to libdebug.

Source code in libdebug/debugger/debugger.py
def wait_for_gdb(self: Debugger) -> None:
    """Waits for the GDB process to migrate back to libdebug."""
    self._internal_debugger.wait_for_gdb()

watchpoint(position, condition='w', length=1, callback=None, file='hybrid')

Sets a watchpoint at the specified location. Internally, watchpoints are implemented as breakpoints.

Parameters:

Name Type Description Default
position int | bytes

The location of the breakpoint.

required
condition str

The trigger condition for the watchpoint (either "w", "rw" or "x"). Defaults to "w".

'w'
length int

The size of the word in being watched (1, 2, 4 or 8). Defaults to 1.

1
callback None | bool | Callable[[ThreadContext, Breakpoint], None]

A callback to be called when the watchpoint is hit. If True, an empty callback will be set. Defaults to None.

None
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/debugger/debugger.py
def watchpoint(
    self: Debugger,
    position: int | str,
    condition: str = "w",
    length: int = 1,
    callback: None | bool | Callable[[ThreadContext, Breakpoint], None] = None,
    file: str = "hybrid",
) -> Breakpoint:
    """Sets a watchpoint at the specified location. Internally, watchpoints are implemented as breakpoints.

    Args:
        position (int | bytes): The location of the breakpoint.
        condition (str, optional): The trigger condition for the watchpoint (either "w", "rw" or "x"). Defaults to "w".
        length (int, optional): The size of the word in being watched (1, 2, 4 or 8). Defaults to 1.
        callback (None | bool | Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the watchpoint is hit. If True, an empty callback will be set. Defaults to None.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    return self._internal_debugger.breakpoint(
        position,
        hardware=True,
        condition=condition,
        length=length,
        callback=callback,
        file=file,
    )

wp(position, condition='w', length=1, callback=None, file='hybrid')

Alias for the watchpoint method.

Sets a watchpoint at the specified location. Internally, watchpoints are implemented as breakpoints.

Parameters:

Name Type Description Default
position int | bytes

The location of the breakpoint.

required
condition str

The trigger condition for the watchpoint (either "w", "rw" or "x"). Defaults to "w".

'w'
length int

The size of the word in being watched (1, 2, 4 or 8). Defaults to 1.

1
callback Callable[[ThreadContext, Breakpoint], None]

A callback to be called when the watchpoint is hit. Defaults to None.

None
file str

The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).

'hybrid'
Source code in libdebug/debugger/debugger.py
def wp(
    self: Debugger,
    position: int | str,
    condition: str = "w",
    length: int = 1,
    callback: None | Callable[[ThreadContext, Breakpoint], None] = None,
    file: str = "hybrid",
) -> Breakpoint:
    """Alias for the `watchpoint` method.

    Sets a watchpoint at the specified location. Internally, watchpoints are implemented as breakpoints.

    Args:
        position (int | bytes): The location of the breakpoint.
        condition (str, optional): The trigger condition for the watchpoint (either "w", "rw" or "x"). Defaults to "w".
        length (int, optional): The size of the word in being watched (1, 2, 4 or 8). Defaults to 1.
        callback (Callable[[ThreadContext, Breakpoint], None], optional): A callback to be called when the watchpoint is hit. Defaults to None.
        file (str, optional): The user-defined backing file to resolve the address in. Defaults to "hybrid" (libdebug will first try to solve the address as an absolute address, then as a relative address w.r.t. the "binary" map file).
    """
    return self._internal_debugger.breakpoint(
        position,
        hardware=True,
        condition=condition,
        length=length,
        callback=callback,
        file=file,
    )