view test/com/sun/jdi/ShellScaffold.sh @ 14150:5a553039e9fc

8151887: com/sun/jdi/RedefineClearBreakpoint.sh failed with timeout Summary: Take timeout factor into account Reviewed-by: dsamersoff
author sla
date Tue, 22 Mar 2016 19:29:25 +0100
parents a4299d47bd00
children 98ba72ec08b2
line wrap: on
line source
#!/bin/sh

#
# Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#

#
#
# jtreg runs this in a scratch dir.
# It (and runregress -no) sets these env vars:
#    TESTSRC:      The dir that contains this file
#    TESTCLASSES:  Where .class files are compiled to
#    TESTJAVA:     The jdk to run
#
# This is a 'library' script that is included by
# shell script test cases that want to run a .java file as the debuggee
# and use jdb as the debugger.  This file contains
# several functions that support such a test.

# The caller script can also set these shell vars before
# including this file:
#    pkg=<package name>       To use a package, define it here and put
#                                package $pkg
#                             in your java file
#    classname=<classnam>     Omit this to use the default class name, 'shtest'.

#    compileOptions=<string>  compile options for at least the first compile, 
#                             eg, compileOptions=-g
#    compileOptions2=<string> Options for the 2nd, ..., compile. compileOptions1
#                             is used if this is not set.  To use no compile
#                             options for the 2nd ... compiles, do 
#                             compileOptions2=none
#
#    mode=-Xcomp or mode=-Xint to run in these modes.  These should not
#                              really be used since the tests are normally
#                              run in both modes.
#    javacCmd=path-to-javac    to use a non-standard javac for compiling
#    compileOptions=<string>   Options to pass to javac
#
# See RedefineException.sh as an example of a caller script.
#
# To do RedefineClasses operations, embed @1 tags in the .java
# file to tell this script how to modify it to produce the 2nd
# version of the .class file to be used in the redefine operation.
# Here are examples of each editting tag and what change
# it causes in the new file.  Note that blanks are not preserved
# in these editing operations.
#
# @1 uncomment
#  orig:   // @1 uncomment   gus = 89;
#  new:         gus = 89;
#
# @1 commentout
#  orig:   gus = 89      // @1 commentout
#  new: // gus = 89      // @1 commentout
#
# @1 delete
#  orig:  gus = 89      // @1 delete
#  new:   entire line deleted
#
# @1 newline
#  orig:  gus = 89;     // @1 newline gus++;
#  new:   gus = 89;     //
#         gus++;
#
# @1 replace
#  orig:  gus = 89;     // @1 replace gus = 90;
#  new:   gus = 90;
#
# The only other tag supported is @1 breakpoint.  The setbkpts function
# sets bkpts at all lines that contain this string.
# 
# Currently, all these tags are start with @1.  It is envisioned that this script
# could be ehanced to allow multiple cycles of redefines by allowing
# @2, @3, ... tags.  IE, processing the @i tags in the ith version of
# the file will produce the i+1th version of the file.
# 
# There are problem with jtreg leaving behind orphan java and jdb processes
# when this script is run.  Sometimes, on some platforms, it just doesn't
# get them all killed properly.
# The solution is to put a magic word in the cmd lines of background java
# and jdb processes this script launches.  We can then do the right kind
# of ps cmds to find all these processes and kill them.  We do this by
# trapping the completion of this script.
#
# An associated problem is that our trap handler (cleanup) doesn't
# always get called when jtreg terminates a test.  This can leave tests
# hanging but following tests should run ok because each test uses
# unique names for the port and temp files (based on the PID returned
# by $$).
#
# mks 6.2a on win 98 presents two problems:
#   $! returns the PID as a negative number whereas ps returns
#      it in the form 0xFFF....  This means our trick of 
#      of using $! to get the PIDs of the jdb and debuggee processes
#      doesn't work.  This will cause some error cases to fail
#      with a jtreg timeout instead of failing more gracefully.
#
#   There is no form of the ps command that will show the whole
#   cmd line.  Thus, the magic keyword trick doesn't work.  We
#   resort to just killing java.exe and jdb.exes
#
# pid usage:
#   debuggeepid: used in jdb process to detect if debuggee has died.
#                - waitForDebuggeeMsg: fail if debuggee is gone
#
#   jdbpid:   dofail: used to detect if in main process or jdb process
#             waitforfinish: quit if the jdb process is gone

#killcmd=/bin/kill
killcmd=kill

# This can be increased if timing seems to be an issue.
sleep_seconds=1
if [ -n "$TIMEOUT_FACTOR" ] ; then
  sleep_seconds=$(echo $TIMEOUT_FACTOR $sleep_seconds | awk '{printf "%d\n", int($1 * $2)}')
fi

echo "ShellScaffold.sh: Version" >& 2
topPid=$$

# Be careful to echo to >& in these general functions.
# If they are called from the functions that are sending
# cmds to jdb, then stdout is redirected to jdb.
cleanup()
{
    if [ -r "$failFile" ] ; then
        ls -l "$failFile" >&2
        echo "<cleanup:_begin_failFile_contents>" >&2
        cat "$failFile" >&2
        echo "<cleanup:_end_failFile_contents>" >&2
    fi

    # Kill all processes that have our special
    # keyword in their cmd line.
    killOrphans cleanup $jdbKeyword
    killOrphans cleanup $debuggeeKeyword
}

# Kill all processes with $2 in their cmd lines
# Print a msg about this using $1 as the prefix
killOrphans()
{
    str=$2

    if [ -z "$isCygwin" ] ; then
        toBeKilled=`$psCmd | $grep -v grep | $grep -i $str | awk '{print $1}' | tr '\n\r' '  '`
    else
        # The cygwin ps command doesn't show the options passed to a cmd.
        # We will use jps to get the win PID of the command, and
        # then use ps to find the cygwin pid to be killed.
        # The form of a ps output line is
        # ^   ddddd    dddd    dddd    dddd.*
        # where the 4th digits are the win pid and the first 
        # are the cygwin pid.
        if [ -r "$jdk/bin/$jstack" ] ; then
            winPid=`$jdk/bin/jps -v | $grep -i $str | sed -e 's@ .*@@'`
            if [ ! -z "$winPid" ] ; then
                # Here is a way to kill using a win cmd and the win PID.
                #echo "$1: taskkill /F $winPid"  >& 2
                #taskkill /F /PID $winPid

                toBeKilled=`$psCmd | $grep -v grep | \
                            $grep '^ +[0-9]+ +[0-9]+ +[0-9]+ +'"$winPid" |\
                            awk '{print $1}' | tr '\n\r' '  '`
            fi
        else
            # Well, too bad - we can't find what to kill.  
            toBeKilled=
        fi
    fi

    if [ ! -z "$toBeKilled" ] ; then
        echo "$1: kill -9 $toBeKilled"  >& 2
        kill -9 $toBeKilled
    fi
}

# Returns 0 if $1 is the pid of a running process
findPid()
{
    if [ -z "$1" ] ; then
        return 1
    fi

    case "$osname" in
        SunOS | AIX)
            $psCmd | $grep '^ *'"$1 " > $devnull 2>&1
            res=$?
            ;;
        Windows* | CYGWIN*)
            # Don't use ps on cygwin since it sometimes misses
            # some processes (!).
            tasklist /NH | $grep " $1 " > $devnull 2>&1
            res=$?
            ;;
       *)
            #   Never use plain 'ps', which requires a "controlling terminal"
            #     and will fail  with a "ps: no controlling terminal" error.
            #     Running under 'rsh' will cause this ps error.
            $psCmd -e | $grep '^ *'"$1 " > $devnull 2>&1
            res=$?
            ;;
    esac
    return $res
}

setup()
{
    failed=
    # This is used to tag each java and jdb cmd we issue so
    # we can kill them at the end of the run.

    orphanKeyword=HANGINGJAVA-$$
    debuggeeKeyword=${orphanKeyword}_DEB
    jdbKeyword=${orphanKeyword}_JDB
    baseArgs=-D${debuggeeKeyword}
    if [ -z "$TESTCLASSES" ] ; then
        echo "--Warning:  TESTCLASSES is not defined; using TESTCLASSES=."
        echo "  You should run: "
        echo "    runregress $0 -no"
        echo "  or"
        echo "    (setenv TESTCLASSES .; $0 $*)"
        TESTCLASSES=.
    fi
    if [ ! -z "$TESTJAVA" ] ; then
        jdk="$TESTJAVA"
    else
        echo "--Error: TESTJAVA must be defined as the pathname of a jdk to test."
        exit 1
    fi

    ulimitCmd=
    osname=`uname -s`
    isCygwin=
    case "$osname" in
        Windows* | CYGWIN*)
            devnull=NUL
            case "$osname" in
                CYGWIN*)
                    isCygwin=1
                    devnull=/dev/null
                    ;;
            esac

            if [ -r $jdk/bin/dt_shmem.dll ] ; then
                transport=dt_shmem
                address=kkkk.$$
            else
                transport=dt_socket
                address=
            fi
            baseArgs="$baseArgs -XX:-ShowMessageBoxOnError"
            # jtreg puts \\s in TESTCLASSES and some uses, eg. echo
            # treat them as control chars on mks (eg \t is tab)
            # Oops; windows mks really seems to want this cat line
            # to start in column 1
            if [ -w "$SystemRoot" ] ; then
                tmpFile=$SystemRoot/tmp.$$
            elif [ -w "$SYSTEMROOT" ] ; then
                tmpFile=$SYSTEMROOT/tmp.$$
            else
                tmpFile=tmp.$$
            fi
cat <<EOF >$tmpFile
$TESTCLASSES
EOF
            TESTCLASSES=`cat $tmpFile | sed -e 's@\\\\@/@g'`
            rm -f $tmpFile
            # on mks
            grep=egrep
            psCmd=ps
            jstack=jstack.exe
            ;;
       SunOS | Linux | Darwin | AIX)
         transport=dt_socket
         address=
         devnull=/dev/null
         grep=egrep
         jstack=jstack
         # On linux, core files take a long time, and can leave
         # zombie processes
         if [ "$osname" = SunOS ] ; then
             # Experiments show Solaris '/usr/ucb/ps -axwww' and
             # '/usr/bin/pgrep -f -l' provide the same small amount of the
             # argv string (PRARGSZ=80 in /usr/include/sys/procfs.h)
             #  1) This seems to have been working OK in ShellScaffold.
             #  2) OpenSolaris does not provide /usr/ucb/ps, so use pgrep
             #     instead
             # The alternative would be to use /usr/bin/pargs [pid] to get
             # all the args for a process, splice them back into one
             # long string, then grep.
             UU=`/usr/xpg4/bin/id -u -n`
             psCmd="pgrep -f -l -U $UU"
         else
             ulimit -c 0
             # See bug 6238593.
             psCmd="ps axwww"
         fi
         ;;
       *)
         echo "--Error:  Unknown result from 'uname -s':  $osname"
         exit 1
         ;;
    esac


    tmpFileDir=$TESTCLASSES/aa$$
    TESTCLASSES=$tmpFileDir

    mkdir -p $tmpFileDir

    # This must not contain 'jdb' or it shows up
    # in grep of ps output for some platforms
    jdbOutFile=$tmpFileDir/jxdbOutput.txt
    rm -f $jdbOutFile
    touch $jdbOutFile

    debuggeeOutFile=$tmpFileDir/debuggeeOutput.txt
    failFile=$tmpFileDir/testFailed
    debuggeepidFile=$tmpFileDir/debuggeepid
    rm -f $failFile $debuggeepidFile
    if [ -f "$failFile" ]; then
        echo "ERROR: unable to delete existing failFile:" >&2
        ls -l "$failFile" >&2
    fi

    if [ -z "$pkg" ] ; then
        pkgSlash=
        pkgDot=
        redefineSubdir=.
    else
        pkgSlash=$pkg/
        pkgDot=$pkg.
        redefineSubdir=$pkgSlash
    fi
    if [ -z "$classname" ] ; then
        classname=shtest
    fi

    if [ -z "$java" ] ; then
        java=java
    fi

    if [ -z "$jdb" ] ; then
        jdb=$jdk/bin/jdb
    fi

####################################################3
####################################################3
####################################################3
####################################################3
#  sol:  this gets all processes killed but 
#        no jstack
#  linux same as above
#  win mks:  No dice; processes still running
    trap "cleanup" 0 1 2 3 4 6 9 10 15

    jdbOptions="$jdbOptions -J-D${jdbKeyword}"
}

docompile()
{
    if [ "$compile" = 0 ] ; then
        return
    fi
    saveDir=`pwd`
    cd $tmpFileDir
    rm -f *.java
    createJavaFile $classname

    # Compile two versions of the file, the original and with the
    # indicated lines modified.
    cp $classname.java.1 $classname.java
    echo "--Compiling first version of `pwd`/$classname.java with options: $compileOptions"
    # Result is in $pkgSlash$classname.class

    if [ -z "$javacCmd" ] ; then
        javacCmd=$jdk/bin/javac
    fi

    echo "compiling " `ls *.java`
    $javacCmd $compileOptions -d . *.java
    if [ $? != 0 ] ; then
       dofail "First compile failed"
    fi
    if [ -r vers1 ] ; then
        rm -rf vers1
    fi
    mkdir -p vers1
    mv *.class vers1
    if [ ! -z "$compileOptions2" ] ; then
        if [ "$compileOptions2" = none ] ; then
            compileOptions=
        else
            compileOptions=$compileOptions2
        fi
    fi

    while [ 1 = 1 ] ; do
        # Not really a loop; just a way to avoid goto
        # by using breaks
        sed -e '/@1 *delete/ d' \
            -e 's! *// *@1 *uncomment!     !' \
            -e 's!\(.*@1 *commentout\)!//\1!' \
            -e 's/@1 *newline/\
                 /' \
            -e 's/.*@1 *replace//' \
            $classname.java.1  >$classname.java

        cmp -s $classname.java.1 $classname.java
        if [ $? = 0 ] ; then
            break
        fi
        echo 
        echo "--Compiling second version of `pwd`/$classname.java with $compileOptions"
        $javacCmd $compileOptions -d . $classname.java
        if [ $? != 0 ] ; then
            dofail "Second compile failed"
        fi
        if [ -r vers2 ] ; then
            rm -rf vers2
        fi
        mkdir -p vers2
        mv *.class vers2
        mv $classname.java $classname.java.2
        cp $classname.java.1 $classname.java

        ###### Do the same for @2, and @3 allowing 3 redefines to occur.
        ###### If I had more time to write sed cmds, I would do
        ###### this in a loop.  But, I don't think we will ever need
        ###### more than 3 redefines.
        sed -e '/@2 *delete/ d' \
            -e 's! *// *@2 *uncomment!     !' \
            -e 's!\(.*@2 *commentout\)!//\1!' \
            -e 's/@2 *newline/\
                 /' \
            -e 's/.*@2 *replace//' \
            $classname.java.2 >$classname.java
        cmp -s $classname.java.2 $classname.java
        if [ $? = 0 ] ; then
            break
        fi
        echo 
        echo "--Compiling third version of `pwd`/$classname.java with $compileOptions"
        $javacCmd $compileOptions -d . $classname.java
        if [ $? != 0 ] ; then
            dofail "Third compile failed"
        fi
        if [ -r vers3 ] ; then
            rm -rf vers3
        fi
        mkdir -p vers3
        mv *.class vers3
        mv $classname.java $classname.java.3
        cp $classname.java.1 $classname.java

        ########
        sed -e '/@3 *delete/ d' \
            -e 's! *// *@3 *uncomment!     !' \
            -e 's!\(.*@3 *commentout\)!//\1!' \
            -e 's/@3 *newline/\
                    /' \
            -e 's/.*@3 *replace//' \
            $classname.java.3 >$classname.java
        cmp -s $classname.java.3 $classname.java
        if [ $? = 0 ] ; then
            break
        fi
        echo 
        echo "--Compiling fourth version of `pwd`/$classname.java with $compileOptions"
        $javacCmd $compileOptions -d . $classname.java
        if [ $? != 0 ] ; then
            dofail "fourth compile failed"
        fi
        if [ -r vers4 ] ; then
            rm -rf vers4
        fi
        mkdir -p vers4
        mv *.class vers4
        mv $classname.java $classname.java.4
        cp $classname.java.1 $classname.java
        break
        fgrep @4 $classname.java
        if [ $? = 0 ] ; then
            echo "--Error: @4 and above are not yet allowed"
            exit 1
        fi
    done

    cp vers1/* $redefineSubdir
    cd $saveDir
}

# Send a cmd to jdb and wait for the jdb prompt to appear.
# We don't want to allow > as a prompt because if the debuggee
# runs for awhile after a command, jdb will show this prompt
# but is not really ready to accept another command for the
# debuggee - ie, a cont in this state will be ignored.
# If it ever becomes necessary to send a jdb command before
# a  main[10] form of prompt appears, then this
# code will have to be modified.
#
# Specify $1 = allowExit to show that the command given
# allows JDB to exit
cmd()
{
    allowExit=
    case "$1" in
        allowExit)
            allowExit="allowExit"
            shift
            ;;
        exitJdb)
            # Quit JDB only with this cmd() invocation
            echo "--Sending cmd: quit" >& 2
            echo quit
            echo "--Quit cmd was sent" >& 2
            # See 6562090. Maybe there is a way that the exit
            # can cause jdb to not get the quit.
            sleep 5

            # The exit code value here doesn't matter since this function
            # is called as part of a pipeline and it is not the last command
            # in the pipeline.
            exit 1
            ;;
    esac
    command=$*

    if [ -z "$command" ] ; then
        dofail "Command can't be a null string. Test failure"
    fi
    if [ "$command" = "quit" -o "$command" = "exit" ] ; then
        # We don't want the test to manually quit jdb,
        # we will do it in the end automatically
        dofail "It's not allowed to send quit or exit commands from the test"
    fi
    if [ -r "$failFile" ] ; then
        # failFile exists, it's better to finish execution
        dofinish "quit"
    fi

    # $jdbOutFile always exists here and is non empty
    # because after starting jdb, we waited 
    # for the prompt.
    fileSize=`wc -c $jdbOutFile | awk '{ print $1 }'`
    echo "--Sending cmd: " $command >&2

    # jjh: We have a few intermittent failures here.
    # It is as if every so often, jdb doesn't
    # get the first cmd that is sent to it here.  
    # (actually, I have seen it get the first cmd ok,
    # but then not get some subsequent cmd).
    # It seems like jdb really doesn't get the cmd; jdb's response
    # does not appear in the jxdboutput file. It contains:
    # main[1] 
    # The application has been disconnected

    # Is it possible
    # that jdb got the cmd ok, but its response didn't make
    # it to the jxdboutput file?  If so, why did 'The application
    # has been disconnected' make it?

    # This causes the following loop to timeout and the test to fail.
    # The above echo works because the cmd (stop at ...)
    # is in the System.err shown in the .jtr file.
    # Also, the cmd is shown in the 'jdb never responded ...'
    # msg output below after the timeout.
    # And, we know jdb is started because the main[1] output is in the .jtr
    # file.  And, we wouldn't have gotten here if mydojdbcmds hadn't
    # seen the ].  
    echo $command

    # Now we have to wait for the next jdb prompt.  We wait for a pattern
    # to appear in the last line of jdb output.  Normally, the prompt is
    #
    # 1) ^main[89] @
    #
    # where ^ means start of line, and @ means end of file with no end of line
    # and 89 is the current command counter. But we have complications e.g.,
    # the following jdb output can appear:
    #
    # 2) a[89] = 10
    #
    # The above form is an array assignment and not a prompt.
    #
    # 3) ^main[89] main[89] ...
    #
    # This occurs if the next cmd is one that causes no jdb output, e.g.,
    # 'trace methods'.
    #
    # 4) ^main[89] [main[89]] .... > @
    #
    # jdb prints a > as a prompt after something like a cont.
    # Thus, even though the above is the last 'line' in the file, it
    # isn't the next prompt we are waiting for after the cont completes.
    # HOWEVER, sometimes we see this for a cont command:
    #
    #   ^main[89] $
    #      <lines output for hitting a bkpt>
    #
    # 5) ^main[89] > @
    #
    # i.e., the > prompt comes out AFTER the prompt we we need to wait for.
    #
    # So, how do we know when the next prompt has appeared??
    # 1.  Search for 
    #         main[89] $
    #     This will handle cases 1, 2, 3
    # 2.  This leaves cases 4 and 5.
    #
    # What if we wait for 4 more chars to appear and then search for
    #
    #    main[89] [>]$
    #
    # on the last line?
    #
    # a.  if we are currently at
    #
    #       ^main[89] main[89] @
    #
    #     and a 'trace methods comes in, we will wait until at least
    #
    #       ^main[89] main[89] main@
    #
    #     and then the search will find the new prompt when it completes.
    #
    # b.  if we are currently at
    #
    #       ^main[89] main[89] @
    #
    #     and the first form of cont comes in, then we will see
    #
    #       ^main[89] main[89] > $
    #       ^x@
    #
    #     where x is the first char of the msg output when the bkpt is hit
    #     and we will start our search, which will find the prompt
    #     when it comes out after the bkpt output, with or without the
    #     trailing >
    #

    # wait for 4 new chars to appear in the jdb output
    count=0
    desiredFileSize=`expr $fileSize + 4`
    msg1=`echo At start: cmd/size/waiting : $command / $fileSize / \`date\``
    while [ 1 = 1 ] ; do
        newFileSize=`wc -c $jdbOutFile | awk '{ print $1 } '`
        #echo jj: desired = $desiredFileSize, new = $newFileSize >& 2

        done=`expr $newFileSize \>= $desiredFileSize`
        if [ $done = 1 ] ; then
            break
        fi
        sleep ${sleep_seconds}
        count=`expr $count + 1`
        if [ $count = 30 -o $count = 60 ] ; then
            # record some debug info.
            echo "--DEBUG: jdb $$ didn't responded to command in $count secs: $command" >& 2
            echo "--DEBUG:" $msg1 >& 2
            echo "--DEBUG: "done size/waiting : / $newFileSize  / `date` >& 2
            echo "-- $jdbOutFile follows-------------------------------" >& 2
            cat $jdbOutFile >& 2
            echo "------------------------------------------" >& 2
            dojstack
            #$psCmd | sed -e '/com.sun.javatest/d' -e '/nsk/d' >& 2
            if [ $count = 60 ] ; then
                dofail "jdb never responded to command: $command"
            fi
        fi
    done
    # Note that this assumes just these chars in thread names.
    waitForJdbMsg '[a-zA-Z0-9_-][a-zA-Z0-9_-]*\[[1-9][0-9]*\] [ >]*$' 1 $allowExit
}

setBkpts()
{
    # Can set multiple bkpts, but only in one class.
    # $1 is the bkpt name, eg, @1
    allLines=`$grep -n "$1 *breakpoint" $tmpFileDir/$classname.java.1 | sed -e 's@^\([0-9]*\).*@\1@g'`
    for ii in $allLines ; do
        cmd "stop at $pkgDot$classname:$ii"
    done
}

runToBkpt()
{
    # Don't pass allowExit here as we don't want JDB to unexpectedly exit
    cmd run
    # Don't need to do this - the above waits for the next prompt which comes out
    # AFTER the Breakpoint hit message.
    # Wait for jdb to hit the bkpt
    #waitForJdbMsg "Breakpoint hit" 5
}

contToBkpt()
{
    # Don't pass allowExit here as we don't want JDB to unexpectedly exit
    cmd cont
    # Don't need to do this - the above waits for the next prompt which comes out
    # AFTER the Breakpoint hit message.
    # Wait for jdb to hit the bkpt
    #waitForJdbMsg "Breakpoint hit" 5
}


# Wait until string $1 appears in the output file, within the last $2 lines
# If $3 is allowExit, then don't fail if jdb exits before
# the desired string appears.
waitForJdbMsg()
{
    # This can be called from the jdb thread which doesn't
    # have access to $debuggeepid, so we have to read it from the file.
    nlines=$2
    allowExit="$3"
    myCount=0
    timeLimit=40  # wait a max of this many secs for a response from a jdb command
    while [ 1 = 1 ] ; do 
        if [  -r $jdbOutFile ] ; then
            # Something here causes jdb to complain about Unrecognized cmd on x86.
            tail -$nlines $jdbOutFile | $grep -s "$1" > $devnull 2>&1
            if [ $? = 0 ] ; then
                # Found desired string
                break
            fi
        fi
        tail -2 $jdbOutFile | $grep -s "The application exited" > $devnull 2>&1
        if [ $? = 0 ] ; then
            # Found 'The application exited'
            echo "--JDB finished: The application exited" >&2
            if [ ! -z "$allowExit" ] ; then
                # Exit is allowed
                dofinish
            fi
            # Otherwise, it is an error if we don't find $1
            if [  -r $jdbOutFile ] ; then 
                tail -$nlines $jdbOutFile | $grep -s "$1" > $devnull 2>&1
                if [ $? = 0 ] ; then
                    break
                fi
            fi
            dofail "JDB unexpectedly finished: Waited for jdb msg $1, but it never appeared"
        fi

        sleep ${sleep_seconds}
        findPid $topPid
        if [ $? != 0 ] ; then
            echo "--Top process ($topPid) is dead.  We better die too" >&2
            dojstack
            exit 1
        fi

        myCount=`expr $myCount + ${sleep_seconds}`
        if [ $myCount -gt $timeLimit ] ; then
            echo "--Fail: waitForJdbMsg timed out after $timeLimit seconds, looking for /$1/, in $nlines lines; exitting" >> $failFile
            echo "vv jdbOutFile  vvvvvvvvvvvvvvvvvvvvvvvvvvvv" >& 2
            cat $jdbOutFile >& 2
            echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >& 2
            dojstack
            exit 1
        fi
    done

}

# Finishes JDB execution
# Specify command to finish if it's needed
dofinish()
{
    if [ ! -z "$*" ] ; then
        echo "--Finish execution with sending \"$*\" command to JDB" >&2
        cmd "exitJdb" "$*"
    else
        echo "--Finish without sending \"quit\" command to JDB" >&2
    fi
    exit 0
}

# $1 is the string to print.  If $2 exists,
# it is the name of a file to print, ie, the name
# of the file that contains the $1 string.
dofail()
{
    if [ ! -z "$jdbpid" ] ; then
        # we are in the main process instead of the jdb process
        echo " " >> $failFile
        echo "--Fail: main: $*" >> $failFile
    else
        # Kill the debuggee ; it could be hung so
        # we want to get rid of it as soon as possible.
        killOrphans "killing debuggee" $debuggeeKeyword
        # Kill debugger, it could be hung
        killOrphans "killing debugger" $jdbKeyword

        echo " "  >>$failFile
        echo "--Fail: $*" >> $failFile
    fi
    if [ ! -z "$2" ] ; then
        echo  "---- contents of $2 follows -------" >> $failFile
        cat "$2" >> $failFile
        echo "---------------" >>$failFile
    fi
    exit 1
}


redefineClass()
{
    if [ -z "$1" ] ; then
        vers=2
    else
        vers=`echo $1 | sed -e 's/@//'`
        vers=`expr $vers + 1`
    fi

    cmd "redefine $pkgDot$classname $tmpFileDir/vers$vers/$classname.class"

    cp $tmpFileDir/$classname.java.$vers \
       $tmpFileDir/$classname.java
}

mydojdbCmds()
{
   # Wait for jdb to start before we start sending cmds
   waitForJdbMsg ']' 1
   # Send commands from the test
   dojdbCmds
   # Finish jdb with quit command
   dofinish "quit"
}

startJdb()
{
    if [ ! -r "$jdb" -a ! -r "$jdb.exe" ] ; then
        dofail "$jdb does not exist"
    fi
    echo
    echo "--Starting jdb, address=$address"
    if [ -z "$address" ] ; then
       # Let jdb choose the port and write it to stdout
       mydojdbCmds | $jdb $jdbOptions -listenany | tee $jdbOutFile &

       while [ 1 ] ; do
           lastLine=`$grep 'Listening at address' $jdbOutFile`
           if [ ! -z "$lastLine" ] ; then
               break
           fi
           sleep 1
       done
       # jjh: we got the address ok, and seemed to start the debuggee
       address=`echo $lastLine | sed -e 's@.*: *@@'`
    else
       mydojdbCmds | $jdb $jdbOptions -listen $address | tee $jdbOutFile &
    fi
    #echo address = $address


    # this gets the pid of tee, at least it does on solaris
    jdbpid=$!

    # This fails on linux because there is an entry for each thread in jdb
    # so we get a list of numbers in jdbpid
    # jdbpid=`$psCmd | $grep -v grep | $grep ${orphanKeyword}_JDB | awk '{print $1}'  | tr '\n\r' '  '`
}

startDebuggee()
{
    args="$TESTVMOPTS $TESTJAVAOPTS"

    if [ ! -z "$args" ] ; then
       echo "--Starting debuggee with args from TESTVMOPTS and/or TESTJAVAOPTS: $args"
    else
       echo "--Starting debuggee"
    fi

    debuggeepid=
    waitForJdbMsg Listening 4

    beOption="-agentlib:jdwp=transport=$transport,address=$address,server=n,suspend=y" 
#   beOption="-Xdebug -Xrunjdwp:transport=$transport,address=$address,server=n,suspend=y"

    thecmd="$jdk/bin/$java $mode -classpath $tmpFileDir $baseArgs $args \
            -Djtreg.classDir=$TESTCLASSES \
            -showversion \
             $beOption \
             $pkgDot$classname"
    echo "Cmd: $thecmd"

    sh -c "$thecmd | tee $debuggeeOutFile" &

    # Note that the java cmd and the tee cmd will be children of
    # the sh process.  We can use that to find them to kill them.
    debuggeepid=$!

    # Save this in a place where the jdb process can find it.
    # Note that it is possible for the java cmd to abort during startup
    # due to a bad classpath or whatever.
    echo $debuggeepid > $debuggeepidFile
}

dojstack()
{
    if [ -r "$jdk/bin/$jstack" ] ; then
        # If jstack exists, so will jps
        # Show stack traces of jdb and debuggee as a possible debugging aid.
        jdbCmd=`$jdk/bin/jps -v | $grep $jdbKeyword`
        realJdbPid=`echo "$jdbCmd" | sed -e 's@ TTY.*@@'`
        if [ ! -z "$realJdbPid" ] ; then
            echo "-- jdb process info ----------------------" >&2
            echo "      $jdbCmd"                              >&2
            echo "-- jdb threads: jstack $realJdbPid"         >&2
            $jdk/bin/$jstack $realJdbPid                      >&2
            echo "------------------------------------------" >&2
            echo                                              >&2
        fi
        debuggeeCmd=`$jdk/bin/jps -v | $grep $debuggeeKeyword`
        realDebuggeePid=`echo "$debuggeeCmd" | sed -e 's@ .*@@'`
        if [ ! -z "$realDebuggeePid" ] ; then
            echo "-- debuggee process info ----------------------" >&2
            echo "      $debuggeeCmd"                              >&2
            echo "-- debuggee threads: jstack $moption $realDebuggeePid" >&2
            $jdk/bin/$jstack $realDebuggeePid                      >&2
            echo "============================================="   >&2
            echo                                                   >&2
        fi
    fi
}

waitForFinish()
{
    # This is the main process
    # Wait for the jdb process to finish, or some error to occur

    while [ 1 = 1 ] ; do
        findPid $jdbpid
        if [ $? != 0 ] ; then
            break
        fi

        # (Don't use jdbFailIfPresent here since it is not safe 
        # to call from different processes)
        $grep -s 'Input stream closed' $jdbOutFile > $devnull 2>&1
        if [ $? = 0 ] ; then
            dofail "jdb input stream closed prematurely"
        fi

        # If a failure has occured, quit
        if [ -r "$failFile" ] ; then
            break
        fi

        sleep ${sleep_seconds}
    done

    # jdb exited because its input stream closed prematurely
    # (Don't use jdbFailIfPresent here since it is not safe 
    # to call from different processes)
    $grep -s 'Input stream closed' $jdbOutFile > $devnull 2>&1
    if [ $? = 0 ] ; then
        dofail "jdb input stream closed prematurely"
    fi

    # It is necessary here to avoid the situation when JDB exited but
    # mydojdbCmds() didn't finish because it waits for JDB message
    # in waitForJdbMsg(), at the same time main process will finish
    # the execution with no errors.
    # To avoid that, wait for spawned processes to finish
    case "$osname" in
        SunOS)
            # `wait` function doesn't work in Solaris shell as in bash,
            # so create replacement that finds mydojdbCmds() shell process
            # and waits for its finish
            cmdsPid=
            # get list of processes except main process with $topPid
            processes=`$psCmd | $grep -v "$grep" | $grep -v $topPid | awk '{print $1}'`
            for pid in $processes; do
                # for each process grep its full args string for test name $0
                # $0 contains full test name with path
                pargs -l $pid 2>$devnull | $grep "$0" >$devnull 2>&1
                if [ $? = 0 ] ; then
                    cmdsPid=$pid
                    break
                fi
            done
            echo "--waitForFinish: Waiting for mydojdbCmds() to finish" >&2
            while [ 1 = 1 ] ; do
                findPid $cmdsPid
                if [ $? != 0 ] ; then
                    break
                fi
                sleep ${sleep_seconds}
            done
            ;;
        *)
            echo "--waitForFinish: Waiting for all processes to finish" >&2
            wait
            ;;
    esac

    if [ -r "$failFile" ] ; then
        ls -l "$failFile" >&2
        echo "<waitForFinish:_begin_failFile_contents>" >&2
        cat "$failFile" >&2
        echo "<waitForFinish:_end_failFile_contents>" >&2
        exit 1
    fi
}

# $1 is the filename, $2 is the string to look for,
# $3 is the number of lines to search (from the end)
grepForString()
{
    if [ -z "$3" ] ; then
        theCmd=cat
    else
        theCmd="tail -$3"
    fi

    case "$2" in 
    *\>*)
        # Target string contains a '>' so we better not ignore it
        $theCmd $1 | $grep -s "$2"  > $devnull 2>&1
        stat="$?"
        ;;
    *)
        # Target string does not contain a '>'.
        # NOTE:  if $1 does not end with a new line, piping it to sed
        # doesn't include the chars on the last line.  Detect this
        # case, and add a new line.
        theFile="$1"
        if [ `tail -1 "$theFile" | wc -l | sed -e 's@ @@g'` = 0 ] ; then
            # The target file doesn't end with a new line so we have
            # add one to a copy of the target file so the sed command
            # below can filter that last line.
            cp "$theFile" "$theFile.tmp"
            theFile="$theFile.tmp"
            echo >> "$theFile"
        fi

        # See bug 6220903. Sometimes the jdb prompt chars ('> ') can
        # get interleaved in the target file which can keep us from
        # matching the target string.
        $theCmd "$theFile" | sed -e 's@> @@g' -e 's@>@@g' \
            | $grep -s "$2" > $devnull 2>&1
        stat=$?
        if [ "$theFile" != "$1" ]; then
            # remove the copy of the target file
            rm -f "$theFile"
        fi
        unset theFile
    esac

    return $stat
}

# $1 is the filename, $2 is the regexp to match and return,
# $3 is the number of lines to search (from the end)
matchRegexp()
{
    if [ -z "$3" ] ; then
        theCmd=cat
    else
        theCmd="tail -$3"
    fi

    case "$2" in 
    *\>*)
        # Target string contains a '>' so we better not ignore it
        res=`$theCmd $1 | sed -e "$2"`
        ;;
    *)
        # Target string does not contain a '>'.
        # NOTE:  if $1 does not end with a new line, piping it to sed
        # doesn't include the chars on the last line.  Detect this
        # case, and add a new line.
        theFile="$1"
        if [ `tail -1 "$theFile" | wc -l | sed -e 's@ @@g'` = 0 ] ; then
            # The target file doesn't end with a new line so we have
            # add one to a copy of the target file so the sed command
            # below can filter that last line.
            cp "$theFile" "$theFile.tmp"
            theFile="$theFile.tmp"
            echo >> "$theFile"
        fi

        # See bug 6220903. Sometimes the jdb prompt chars ('> ') can
        # get interleaved in the target file which can keep us from
        # matching the target string.
        res=`$theCmd "$theFile" | sed -e 's@> @@g' -e 's@>@@g' \
            | sed -e "$2"`
        if [ "$theFile" != "$1" ]; then
            # remove the copy of the target file
            rm -f "$theFile"
        fi
        unset theFile
    esac
    return $res
}

# $1 is the filename, $2 is the string to look for,
# $3 is the number of lines to search (from the end)
failIfPresent()
{
    if [ -r "$1" ] ; then
        grepForString "$1" "$2" "$3"
        if [ $? = 0 ] ; then
            dofail "Error output found: \"$2\" in $1" $1
        fi
    fi
}

# $1 is the filename, $2 is the string to look for
# $3 is the number of lines to search (from the end)
failIfNotPresent()
{
    if [ ! -r "$1" ] ; then
        dofail "Required output \"$2\" not found in $1"
    fi
    grepForString "$1" "$2" "$3"
    if [ $? != 0 ] ; then
        dofail "Required output \"$2\" not found in $1" $1
    fi

}

# fail if $1 is not in the jdb output
# $2 is the number of lines to search (from the end)
jdbFailIfNotPresent()
{
    failIfNotPresent $jdbOutFile "$1" $2
}

# fail if $1 is not in the debuggee output
# $2 is the number of lines to search (from the end)
debuggeeFailIfNotPresent()
{
    failIfNotPresent $debuggeeOutFile "$1" $2
}

# fail if $1 is in the jdb output
# $2 is the number of lines to search (from the end)
jdbFailIfPresent()
{
    failIfPresent $jdbOutFile "$1" $2
}

# fail if $1 is in the debuggee output
# $2 is the number of lines to search (from the end)
debuggeeFailIfPresent()
{
    failIfPresent $debuggeeOutFile "$1" $2
}

# match and return the output from the regexp $1 in the debuggee output
# $2 is the number of lines to search (from the end)
debuggeeMatchRegexp()
{
    matchRegexp $debuggeeOutFile "$1" $2
}


# This should really be named 'done' instead of pass.
pass()
{
    if [ ! -r "$failFile" ] ; then
        echo
        echo "--Done: test passed"
        exit 0
    else
        ls -l "$failFile" >&2
        echo "<pass:_begin_failFile_contents>" >&2
        cat "$failFile" >&2
        echo "<pass:_end_failFile_contents>" >&2
    fi
}

runit()
{
    setup
    docompile
    startJdb
    startDebuggee
    waitForFinish

    # in hs_err file from 1.3.1
    debuggeeFailIfPresent "Virtual Machine Error"

    # in hs_err file from 1.4.2, 1.5:  An unexpected error
    debuggeeFailIfPresent "An unexpected error"

    # in hs_err file from 1.4.2, 1.5:  Internal error
    debuggeeFailIfPresent "Internal error"


    # Don't know how this arises
    debuggeeFailIfPresent "An unexpected exception"

    # Don't know how this arises
    debuggeeFailIfPresent "Internal exception"
}