@@ -195,7 +195,7 @@ def debug_assembly(asm, gdbscript=None, vma=None, api=False):
195195
196196 >>> assembly = shellcraft.echo("Hello world!\n")
197197 >>> io = gdb.debug_assembly(assembly)
198- >>> io.recvline()
198+ >>> io.recvline(timeout=1 )
199199 b'Hello world!\n'
200200 """
201201 tmp_elf = make_elf_from_assembly (asm , vma = vma , extract = False )
@@ -230,7 +230,7 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
230230 >>> assembly = shellcraft.echo("Hello world!\n")
231231 >>> shellcode = asm(assembly)
232232 >>> io = gdb.debug_shellcode(shellcode)
233- >>> io.recvline()
233+ >>> io.recvline(timeout=1 )
234234 b'Hello world!\n'
235235 """
236236 if isinstance (data , six .text_type ):
@@ -283,7 +283,7 @@ def _execve_script(argv, executable, env, ssh):
283283 return tmp .name
284284
285285
286- def _gdbserver_args (pid = None , path = None , args = None , which = None , env = None , python_wrapper_script = None ):
286+ def _gdbserver_args (pid = None , path = None , port = 0 , gdbserver_args = None , args = None , which = None , env = None , python_wrapper_script = None ):
287287 """_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
288288
289289 Sets up a listening gdbserver, to either connect to the specified
@@ -292,6 +292,8 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
292292 Arguments:
293293 pid(int): Process ID to attach to
294294 path(str): Process to launch
295+ port(int): Port to use for gdbserver, default: random
296+ gdbserver_args(list): List of additional arguments to pass to gdbserver
295297 args(list): List of arguments to provide on the debugger command line
296298 which(callaable): Function to find the path of a binary.
297299 env(dict): Environment variables to pass to the program
@@ -300,6 +302,11 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
300302 Returns:
301303 A list of arguments to invoke gdbserver.
302304 """
305+ if gdbserver_args is None :
306+ gdbserver_args = list ()
307+ elif not isinstance (gdbserver_args , (list , tuple )):
308+ gdbserver_args = [gdbserver_args ]
309+
303310 if [pid , path , args ].count (None ) != 2 :
304311 log .error ("Must specify exactly one of pid, path, or args" )
305312
@@ -323,7 +330,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
323330
324331 orig_args = args
325332
326- gdbserver_args = [gdbserver , '--multi' ]
333+ gdbserver_args = [gdbserver , '--multi' ] + gdbserver_args
327334 if context .aslr :
328335 gdbserver_args += ['--no-disable-randomization' ]
329336 else :
@@ -351,7 +358,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None, python
351358 else :
352359 gdbserver_args += ['--no-startup-with-shell' ]
353360
354- gdbserver_args += ['localhost:0' ]
361+ gdbserver_args += ['localhost:%d' % port ]
355362 gdbserver_args += args
356363
357364 return gdbserver_args
@@ -416,17 +423,20 @@ def _get_runner(ssh=None):
416423 else : return tubes .process .process
417424
418425@LocalContext
419- def debug (args , gdbscript = None , exe = None , ssh = None , env = None , sysroot = None , api = False , ** kwargs ):
426+ def debug (args , gdbscript = None , gdb_args = None , exe = None , ssh = None , env = None , port = 0 , gdbserver_args = None , sysroot = None , api = False , ** kwargs ):
420427 r"""
421428 Launch a GDB server with the specified command line,
422429 and launches GDB to attach to it.
423430
424431 Arguments:
425432 args(list): Arguments to the process, similar to :class:`.process`.
426433 gdbscript(str): GDB script to run.
434+ gdb_args(list): List of additional arguments to pass to GDB.
427435 exe(str): Path to the executable on disk
428436 env(dict): Environment to start the binary in
429437 ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
438+ port(int): Gdb port to use, default: random
439+ gdbserver_args(list): List of additional arguments to pass to gdbserver
430440 sysroot(str): Set an alternate system root. The system root is used to
431441 load absolute shared library symbol files. This is useful to instruct
432442 gdb to load a local version of binaries/libraries instead of downloading
@@ -480,12 +490,12 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
480490 Send a command to Bash
481491
482492 >>> io.sendline(b"echo hello")
483- >>> io.recvline()
493+ >>> io.recvline(timeout=30 )
484494 b'hello\n'
485495
486496 Interact with the process
487497
488- >>> io.interactive() # doctest: +SKIP
498+ >>> io.interactive(timeout=1 ) # doctest: +SKIP
489499 >>> io.close()
490500
491501 Create a new process, and stop it at '_start'
@@ -504,7 +514,7 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
504514 Send a command to Bash
505515
506516 >>> io.sendline(b"echo hello")
507- >>> io.recvline()
517+ >>> io.recvline(timeout=10 )
508518 b'hello\n'
509519
510520 Interact with the process
@@ -516,53 +526,24 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
516526
517527 >>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh")
518528 >>> io.sendline(b"echo $0")
519- >>> io.recvline()
529+ >>> io.recvline(timeout=10 )
520530 b'\xde\xad\xbe\xef\n'
521531 >>> io.close()
522532
523533 Demonstrate that LD_PRELOAD is respected
524534
525535 >>> io = process(["grep", "libc.so.6", "/proc/self/maps"])
526- >>> real_libc_path = io.recvline().split()[-1]
536+ >>> real_libc_path = io.recvline(timeout=1 ).split()[-1]
527537 >>> io.close()
528538 >>> import shutil
529539 >>> local_path = shutil.copy(real_libc_path, "./local-libc.so") # make a copy of libc to demonstrate that it is loaded
530540 >>> io = gdb.debug(["grep", "local-libc.so", "/proc/self/maps"], gdbscript="continue", env={"LD_PRELOAD": "./local-libc.so"})
531- >>> io.recvline().split()[-1] # doctest: +ELLIPSIS
541+ >>> io.recvline(timeout=1 ).split()[-1] # doctest: +ELLIPSIS
532542 b'.../local-libc.so'
533543 >>> io.close()
534544 >>> os.remove("./local-libc.so") # cleanup
535545
536546
537- Using GDB Python API:
538-
539- .. doctest::
540- :skipif: is_python2
541-
542- Debug a new process
543-
544- >>> io = gdb.debug(['echo', 'foo'], api=True)
545-
546- Stop at 'write'
547-
548- >>> bp = io.gdb.Breakpoint('write', temporary=True)
549- >>> io.gdb.continue_and_wait()
550-
551- Dump 'count'
552-
553- >>> count = io.gdb.parse_and_eval('$rdx')
554- >>> long = io.gdb.lookup_type('long')
555- >>> int(count.cast(long))
556- 4
557-
558- Resume the program
559-
560- >>> io.gdb.continue_nowait()
561- >>> io.recvline()
562- b'foo\n'
563- >>> io.close()
564-
565-
566547 Using SSH:
567548
568549 You can use :func:`debug` to spawn new processes on remote machines as well,
@@ -591,17 +572,63 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
591572
592573 >>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh", ssh=shell)
593574 >>> io.sendline(b"echo $0")
594- >>> io.recvline()
575+ >>> io.recvline(timeout=10 )
595576 b'$ \xde\xad\xbe\xef\n'
596577 >>> io.close()
597578
598579 Using an empty args[0] on a remote process
599580
600581 >>> io = gdb.debug(args=[], gdbscript='continue', exe="/bin/sh", ssh=shell)
601582 >>> io.sendline(b"echo $0")
602- >>> io.recvline()
583+ >>> io.recvline(timeout=10 )
603584 b'$ \n'
604585 >>> io.close()
586+
587+
588+ Using GDB Python API:
589+
590+ .. doctest::
591+ :skipif: is_python2
592+
593+ Debug a new process
594+
595+ >>> io = gdb.debug(['echo', 'foo'], api=True)
596+
597+ or using ssh
598+
599+ >>> shell = ssh('travis', 'example.pwnme', password='demopass')
600+ >>> ssh_io = gdb.debug(['/bin/echo', 'foo'], ssh=shell, api=True)
601+
602+ Stop at 'write'
603+
604+ >>> bp = io.gdb.Breakpoint('write', temporary=True)
605+ >>> io.gdb.continue_and_wait()
606+ >>> ssh_bp = ssh_io.gdb.Breakpoint('write', temporary=True)
607+ >>> ssh_io.gdb.continue_and_wait()
608+
609+ Dump 'count'
610+
611+ >>> count = io.gdb.parse_and_eval('$rdx')
612+ >>> long = io.gdb.lookup_type('long')
613+ >>> int(count.cast(long))
614+ 4
615+ >>> count = ssh_io.gdb.parse_and_eval('$rdx')
616+ >>> long = ssh_io.gdb.lookup_type('long')
617+ >>> int(count.cast(long))
618+ 4
619+
620+ Resume the program
621+
622+ >>> io.gdb.continue_nowait()
623+ >>> io.recvline(timeout=1)
624+ b'foo\n'
625+ >>> io.close()
626+
627+ >>> ssh_io.gdb.continue_nowait()
628+ >>> ssh_io.recvline(timeout=1)
629+ b'foo\n'
630+ >>> ssh_io.close()
631+ >>> shell.close()
605632 """
606633 if isinstance (args , six .integer_types + (tubes .process .process , tubes .ssh .ssh_channel )):
607634 log .error ("Use gdb.attach() to debug a running process" )
@@ -615,8 +642,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
615642 which = _get_which (ssh )
616643 gdbscript = gdbscript or ''
617644
618- if api and runner is not tubes .process .process :
619- raise ValueError ('GDB Python API is supported only for local processes' )
645+ if api and runner is not tubes .process .process and not ssh :
646+ raise ValueError ('GDB Python API is supported only for local and ssh processes' )
620647
621648 args , env = misc .normalize_argv_env (args , env , log )
622649 if env :
@@ -632,17 +659,17 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
632659
633660 if ssh or context .native or (context .os == 'android' ):
634661 if len (args ) > 0 and which (packing ._decode (args [0 ])) == packing ._decode (exe ):
635- args = _gdbserver_args (args = args , which = which , env = env )
662+ args = _gdbserver_args (gdbserver_args = gdbserver_args , args = args , port = port , which = which , env = env )
636663
637664 else :
638665 # GDBServer is limited in it's ability to manipulate argv[0]
639666 # but can use the ``--wrapper`` option to execute commands and catches
640667 # ``execve`` calls.
641668 # Therefore, we use a wrapper script to execute the target binary
642669 script = _execve_script (args , executable = exe , env = env , ssh = ssh )
643- args = _gdbserver_args (args = args , which = which , env = env , python_wrapper_script = script )
670+ args = _gdbserver_args (gdbserver_args = gdbserver_args , args = args , port = port , which = which , env = env , python_wrapper_script = script )
644671 else :
645- qemu_port = random .randint (1024 , 65535 )
672+ qemu_port = port if port != 0 else random .randint (1024 , 65535 )
646673 qemu_user = qemu .user_path ()
647674 sysroot = sysroot or qemu .ld_prefix (env = env )
648675 if not qemu_user :
@@ -671,17 +698,19 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
671698 # Set the .executable on the process object.
672699 gdbserver .executable = exe
673700
674- # Find what port we need to connect to
675701 if ssh or context .native or (context .os == 'android' ):
676- port = _gdbserver_port (gdbserver , ssh )
702+ gdb_port = _gdbserver_port (gdbserver , ssh )
703+ if port != 0 and port != gdb_port :
704+ log .error ("gdbserver port (%d) doesn't equals set port (%d)" % (gdb_port , port ))
705+ port = gdb_port
677706 else :
678707 port = qemu_port
679708
680709 host = '127.0.0.1'
681710 if not ssh and context .os == 'android' :
682711 host = context .adb_host
683712
684- tmp = attach ((host , port ), exe = exe , gdbscript = gdbscript , ssh = ssh , sysroot = sysroot , api = api )
713+ tmp = attach ((host , port ), exe = exe , gdbscript = gdbscript , gdb_args = gdb_args , ssh = ssh , sysroot = sysroot , api = api )
685714 if api :
686715 _ , gdb = tmp
687716 gdbserver .gdb = gdb
@@ -948,7 +977,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
948977 ... detach
949978 ... quit
950979 ... ''')
951- >>> io.recvline()
980+ >>> io.recvline(timeout=10 )
952981 b'Hello from process debugger!\n'
953982 >>> io.sendline(b'echo Hello from bash && exit')
954983 >>> io.recvall()
@@ -975,7 +1004,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
9751004
9761005 Observe the forced line
9771006
978- >>> io.recvline()
1007+ >>> io.recvline(timeout=1 )
9791008 b'Hello from process debugger!\n'
9801009
9811010 Interact with the program in a regular way
@@ -999,7 +1028,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
9991028 ... detach
10001029 ... quit
10011030 ... ''')
1002- >>> io.recvline()
1031+ >>> io.recvline(timeout=10 )
10031032 b'Hello from remote debugger!\n'
10041033 >>> io.sendline(b'echo Hello from bash && exit')
10051034 >>> io.recvall()
@@ -1018,7 +1047,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
10181047 >>> io.recvline(timeout=5) # doctest: +SKIP
10191048 b'Hello from ssh debugger!\n'
10201049 >>> io.sendline(b'This will be echoed back')
1021- >>> io.recvline()
1050+ >>> io.recvline(timeout=1 )
10221051 b'This will be echoed back\n'
10231052 >>> io.close()
10241053 """
0 commit comments