The Question
How do I change a Perl script to allow for both unattended and interactive execution of the child process, such that in the interactive scenario, the user interacts with the GDB just like they would if the Perl script was not also parsing the output? And showing prompts and keyboard presses as the user presses them, not after they press the Enter key.
Details
I have a Perl script that launches child processes and reads the output, taking various actions on the output, but always just printing the output to standard output. Normally, the child process is an unattended process that does not require user input (i.e., does not read from standard input). However, sometimes that child process needs to be changed to run GDB, and GDB does need to both prompt the user with a prompt of "(gdb) " (without printing a newline character), read the users input (which is then terminated by the user pressing the Enter key), process the users command, and emit the resulting output (which may be from its application under GDB control, or from GDB itself). However, the Perl script as currently written reads the GDB prompt but does not print out that prompt immediately, and also does not immediately print out each character of the users input as they type each character (and including any usage of backspace, arrow key stroke handling, etc.), but prints out both the gdb prompt and the users input all at once after the user finally presses the Enter key. Thus, the user has to literally type as if they were blind-folded, and cannot immediately see what they have typed until the Perl script completes the reading of the entire line of output, and then prints out the entire input line. Forcing the user to type blind-folded is the problem.
Constraints
- Changing anything in the "real child process" is not an option, but only the Perl script or any intervening "helper scripts" or utilities (if needed) that the Perl script may choose to use (i.e., temporarily generate via here-documents or similar mechanism.).
- Must not require any version of Perl greater than v5.14.2.
- Changing the Perl script to read character output one character at a time and emitting it, unless it is provably as performant as reading entire lines of output (the size of the output of the real-world scenarios is voluminous).
- Use of heavy-weight tools that are not built into versions of Linux including very old out of date versions of Linux such as RHEL6. This negates the use of Expect, too. It might also negate use of Expect.pm as indicated in Perl execute command, capture and display output but am unsure as that might require installation of additional Perl packages, with its attendent risks.
- Changing the use of the tools that invoke gdb to instead attach to an existing child process after it is running. In some scenarios, the gdb needs to be involved prior to
::main()
in the application to be debugged.
Viable possibilities
- Change the Perl script to allow the standard input to be interacted with via redirection using two pipes or something similar. I am unsure how to go about that, in Perl.
- Change the Perl script to launch a intermediate script as a child process that does the redirection, and then the Perl script reads from the separate pipe. Similar to the above: I'm unsure how to code that.
The Minimal Complete Verifiable Example (MCVE)
The Bash script below writes out all scripts and launches them. You can execute this inside /tmp on Linux. I've not tested this script on any other platform than Linux, so do not expect it to work without modifications.
#!/bin/bash
# Dump out a perl_script script:
#
# The perl_script below runs gdb directly on the sleep command, but
# in the real-world scenario, instead of sleep, there would be a
# complex script that calls other scripts that finally may or may
# not invoke gdb.
#
cat > perl_script <<'EOF'
#!/usr/bin/perl
use warnings;
use strict;
# Just for the question, just call gdb directly, but in real-world
# practice, this command would be some other script that eventually
# executes gdb as a child process:
my $shell_command="gdb sleep 999999";
print "perl_script: Executing $shell_command in a pipe ...\n";
my $error_code; # Leave $error_code undefined here.
open(SUBSHELL_FP, "$shell_command |") || do {
$error_code = -1;
};
# Avoid reading from a pipe that immediately failed, as indicated by undefined $error_code:
if (!defined($error_code)) {
while(<SUBSHELL_FP>) {
print; # print output to the user
# Not shown here is the parsing of the output of <SUBSHELL_FP> for
# various strings and taking action upon them that is unrelated to
# the gdb execution.
# It is this reading of <SUBSHELL_FP> that "hides" the "(gdb)"
# prompt and also the keypress output from the user that is typing
# into the gdb prompt, and only emits it upon the _subsequent_
# call to print above in the next loop iteration.
}
close(SUBSHELL_FP);
$error_code = $?;
}
print "End of run with error code $error_code\n";
EOF
# Make the perl_script executable:
chmod a+x perl_script
# Execute the perl_script:
#
# While this perl_script is running, the user can type "info br"
# (and press the Enter key) to see some output (just to force gdb to
# emit an answer which is that there are no breakpoints). But,
# typing letters into the xterm is NOT displayed, and neither is the
# "(gdb)" prompt, at least at first. Just as soon as the user
# presses the Enter key, the Perl script above prints the prompt and
# the users input (see "print output to the user" above).
#
# Then the user can press CTRL+d to exit the gdb process, which will
# then return to the "read" below.
#
./perl_script
# Here, the user has executed gdb and thus exited the
# perl_script. Indicate that the script has finished:
echo Hit enter to continue
# shellcheck disable=SC2034
read -r dont_care