hostShell Migration

Introduction

From VxWorks 7, the hostShell tool has been replaced with wrdbg. The existing languages supported by the hostShell were :

  • C
  • gdb
  • tcl

With wrdbg, supported languages are :

  • gdb
  • python

Migrating from hostShell to wrdbg should then be a matter of:

  • converting C shell code to gdb commands or python
  • converting tcl code to python

This should allow to have both the gdb commands and python scripting language. Migrating hostShell scripts to wrdbg script is a matter of transforming the C and tcl calls into python.

Using Python

WorkBench 4 comes with python shell with the additional greenlet module (allowing cache consistency mechanisms) where part of the GDB Python APIs have been implemented.

To migrate from hostShell scripts, the gdb.execute() and gdb.STDOUT are the only interfaces needed. Both GDB commands and python knowledge are required.

gdb.execute

gdb.execute(command, from_tty=False, to_string=False, interpreter=False)[source]

Execute a GDB command.

Evaluate command, a basestring, as a GDB CLI command.

Parameter from_tty specifies whether GDB ought to consider this command as having originated from the user invoking it interactively. It must be a bool value. If omitted, it defaults to False.

By default, any output produced by command is sent to GDB’s standard output. If the to_string parameter is True, then output will be collected by gdb.execute() and returned as a basestring. The default is False, in which case the return value is None.

See also

gdb.STDOUT

gdb.STDOUT

gdb.STDOUT = None

Using GDB

WorkBench 4 comes with wrdbg shell, which is a shell emulating the GDB commands to connect and debug a VxWorks 7 target.

Using the wrdbg Commands it is possible to debug a VxWorks 7 target, but data processing of this data is not very easy, we recommand to use the python way to read/parse data from the target.

Migration

For the migration chapter, only the python way is considered.

Target connection

With the hostShell, the way to connect to a target was:

  • boot the target
  • start a target server
  • start the hostShell connected to the target server

Using wrdbg, it should be:

import gdb
connection = gdb.execute('target connect vxworks7:127.0.0.1:44552 '\
                         '-kernel ~/tmp/vxWorks -logdir ~/tmp', False,
                         True)

Module load

The module load – Load a module from FILE gdb command replaces:

  • tcl’s wtxObjModuleLoad
  • C’s ld

When loading a module, a memory space must be attached. For VxWorks 7, memory spaces at Real Time Processes (the Kernel is a Real Time Process).

Once connected to a target, loading a module can be done like:

...
# Load module in the Kernel memory space. Attach to Kernel first.
# Assuming 'module' is the host path to the module we want to load.
gdb.execute('attach Kernel')
gdb.execute('module load ' + module)
...
# Unload the module we loaded earlier
gdb.execute('module unload ' + module)
...
# Detach from kernel
gdb.execute('detach')
...

Load options

The module load – Load a module from FILE command uses options set through the set module load – Set module load options command.

To know the possible options, you have to be attached to the memory space you want to load the module in and issue the help set module load command.

Current options value may be retrieved using show module load – Show the specified module load option command.

Note

The list of options and values may depend on the OS version and the memory space attached.

The result of the show module load – Show the specified module load option command when attached to the Kernel can look like:

LD_SYMBOLS is all
LD_COMMONS is match_all
LD_CPP is auto
LD_MISC is ignore_if_identical
LD_INIT is auto
LD_WEAK is none

Load errors

Module loading may fail, or succeed but return an error message. On failure, the gdb.execute() command raises an Exception.

When module load succeeded but errors have been detected in the module, the message returned by module load – Load a module from FILE describes the error.

Undefined symbols

A simple processing of the load command output and the use of info modules command should help getting the list of undefined symbols for a given module.

# Attach to the kernel

gdb.execute('attach Kernel')

# Check that module does not have undefined symbols.

loadResult = gdb.execute('module load ' + module, False, True)
if 'Error text: S_loadLib_UNDEFINED_REFERENCES' in loadResult:
    undefs = gdb.execute('info modules ' + module + ' -print undef', False,
                         True)
    with open(errfile, 'w') as f:
        f.write(undefs)
    gdb.execute('module unload ' + module)
    # Detach from Kernel
    gdb.execute('detach')
    # Disconnect from the target
    gdb.execute('target disconnect')
    quit()

Module unload

The module unload – Unload a module FILE gdb command replaces:

  • tcl’s wtxObjModuleUnload
  • C’s unld

Unloading a module may be done by: - module path – all modules with given path are unloaded - module ID – only the module with given ID is unloaded

The list of modules loaded so far is retrieved calling for the info modules command.

...
# Load module in the Kernel memory space. Attach to Kernel first.
# Assuming 'module' is the host path to the module we want to load.
gdb.execute('attach Kernel')
gdb.execute('module load ' + module)
...

The list of modules could then look like:

info modules
vxMod.0.6.P2 : vxWorks
vxMod.10929208.1.P2 : C:\tmp\phil.o

The module loaded above (phil.o) could then be removed like:

# Unload the module we loaded earlier
gdb.execute('module unload ' + module)
...
# Detach from kernel
gdb.execute('detach')
...

or:

# Unload the module we loaded earlier
gdb.execute('module unload vxMod.10929208.1.P2)
...
# Detach from kernel
gdb.execute('detach')
...

Unload options

The module unload – Unload a module FILE command uses options set through the set module unload – Set module unload options command.

To know the possible options, you have to be attached to the memory space you want to unload the module from and issue the help set module unload command.

Current options value may be retrieved using show module unload – Show the specified module unload option command.

Note

The list of options and values may depend on the OS version and the memory space attached.

The result of the show module unload – Show the specified module unload option command when attached to the Kernel can look like:

UNLD_CPLUS_XTOR is auto
UNLD_TERM is auto
UNLD_STRATEGY is 0

Task creation

Using the hostShell, spawning a task was done through the C sp() command. With wrdbg, the appropriate command is thread create – Create a thread.

If the task to be started needs a module load, please see the Module load section.

Once the module is loaded in the appropriate memory space, it is possible to start the task using the thread create – Create a thread command. As we want to be as close to GDB pardigm, we used the thread for task. It is possible to modify that by using the alias command though :

gdb.execute('alias task=thread')
gdb.execute('help task create')

When starting task, the entrypoint (the symbol to spwan task at, e.g. main) must be provided. If the entrypoint has parameters, they may be passed to the thread create – Create a thread command.

A task is created stopped at entrypoint, it won’t start until the continue command is used.

To keep track of the created task, it is recommended to get the command output, using the to_string argument of the gdb.execute() function set to True.

...
gdb.execute('alias task=thread')
task = gdb.execute('task create ' + entrypoint + ' 1 2 0', False, True)
taskID = task.split('id = ')[1].split(',')[0]
taskName = task.split('name = ')[1].split(',')[0]

It is also possible to define a function which does that all, and call it for any task spawned:

def taskSpawn(entrypoint, *args):
    # Create task arguments list
    arguments = ''
    for arg in args:
        arguments += ' ' + str(arg)

    # Spawn the task, and get its ID and name.

    task = gdb.execute('thread create ' + entrypoint + arguments, False, True)
    taskID = task.split('id = ')[1].split(',')[0]
    taskName = task.split('name = ')[1].split(',')[0]

    # Wait for the task to be spawned and stopped before issuing any further
    # command

    waitfor = (entrypoint + ' (', ' at ')
    matchesall = False
    timeout = 30
    waits = 0

    # The task is stopped when the "<entrypoint> (.*) at " appears in the
    # thread info of our task (meaning it is stopped at <entrypoint>

    while not matchesall:
        matchesall = True
        cmdix = gdb.STDOUT.tell()
        gdb.execute('info thread')
        output = gdb.STDOUT.getvalue()[cmdix:]
        for match in waitfor:
            if not match in output:
                matchesall = False
        if not matchesall:
            time.sleep(1)
            waits += 1

        # Do not wait forever ...

        if waits > timeout:
            raise Exception('Thread never created')

    # XXX: fle : to emulate the sp() behavior, we should add the following line
    # here:
    #
    # gdb.execute('continue &')
    #
    # This would require changes in the code handling this task though.

    return (taskID.strip(), taskName.strip())

Task options

The task entrypoint and arguments are the only parameters accepted by the thread create – Create a thread command, but it is also possible to set some task options using the set thread create – Set thread arguments command.

Current options value may be retrieved using show thread create – Show a thread option command.

Note

The list of options and values may depend on the OS version and the memory space attached.

The result of the show thread create – Show a thread option command when attached to the Kernel can look like:

AttachChildren is true
StackSize is 20480
ThreadOptions is VX_COPROCS_ALL_TASK

Task debug

A task can be debugged only it has been attached or created (see Task creation).

To attach to a task, use the thread attach – Attach to a thread command.

The list of attachable tasks is given by the ps command:

ps
P2  Kernel
    P2.282207560  ipcom_syslogd
    P2.278455768  tNetConf
    P2.278604944  ipcom_tickd
    P2.278487248  t8

Once created or attached, breakpoints can be added (see Set a breakpoint). Once the breakpoint has been added, it is possible to run the task up until it hits the breakpoint like:

def bpAdd(symbol):
    bp = gdb.execute('break ' + symbol, False, True)
    # Extract the breakpoint id from returned breakpoint string, it
    # should be the second word of the result.

    bpID = bp.split(' ')[1]

    return bpID

def taskSpawn(entrypoint, *args):
    # Create task arguments list
    arguments = ''
    for arg in args:
        arguments += ' ' + str(arg)

    # Spawn the task, and get its ID and name.

    task = gdb.execute('thread create ' + entrypoint + arguments, False, True)
    taskID = task.split('id = ')[1].split(',')[0]
    taskName = task.split('name = ')[1].split(',')[0]

    # Wait for the task to be spawned and stopped before issuing any further
    # command

    waitfor = (entrypoint + ' (', ' at ')
    matchesall = False
    timeout = 30
    waits = 0

    # The task is stopped when the "<entrypoint> (.*) at " appears in the
    # thread info of our task (meaning it is stopped at <entrypoint>

    while not matchesall:
        matchesall = True
        cmdix = gdb.STDOUT.tell()
        gdb.execute('info thread')
        output = gdb.STDOUT.getvalue()[cmdix:]
        for match in waitfor:
            if not match in output:
                matchesall = False
        if not matchesall:
            time.sleep(1)
            waits += 1

        # Do not wait forever ...

        if waits > timeout:
            raise Exception('Thread never created')

    # XXX: fle : to emulate the sp() behavior, we should add the following line
    # here:
    #
    # gdb.execute('continue &')
    #
    # This would require changes in the code handling this task though.

    return (taskID.strip(), taskName.strip())

...
bpID = bpAdd(symbol)

taskID, taskName = taskSpawn(entrypoint, 1, 2, 0)

# Run this task up until we hit our breakpoint

while True:
    hit = gdb.execute('continue', False, True)
    if 'Breakpoint ' + bpID in hit:
        break

Task IOs

Unfortunately, there is no way for now to track the task Inputs/Outputs. Still, as the target agent has the Host FileSystem service, writing a file in the mounted device would make it possible to read it on the host once the task is done.

Unix host

The info hostfs command show the hostf mounted devices:

info hostfs
Host Path : Target Path
-----------------------
/ : /wrdbg/root/

With the example above, if a task on the target writes its IOs to /wrdbg/root/home/myhome/logs/taskios.txt, the file is also readable on the host at /home/myhome/logs/taskios.txt

Windows host

On a Windows host, the output of info hostfs could look like:

info hostfs
Host Path : Target Path
-----------------------
C:\ : /wrdbg/root/C/
D:\ : /wrdbg/root/D/
E:\ : /wrdbg/root/E/
G:\ : /wrdbg/root/G/
H:\ : /wrdbg/root/H/
I:\ : /wrdbg/root/I/
J:\ : /wrdbg/root/J/

Using the following code sample:

# Hostf use example

hostfsmaps = gdb.execute('info hostfs', False, True)

# Remove the first two lines of the "info hostfs" command to use the output

for hostfsmap in hostfsmaps.split('\n')[2:]:
    try:
        hostpath, targetpath = hostfsmap.split(' : ', 1)
        # Only use the host paths on C: and D: or /
        if hostpath.lower().startswith('c:') or \
           hostpath.lower().startswith('d:') or \
           hostpath == '/':
            print targetpath + ' -> ' + hostpath
    except:
        # Ignore when map does not say "hostpath : targetpath"
        pass

Would give an output like:

/wrdbg/root/C/ -> C:\
/wrdbg/root/D/ -> D:\

It is then possible to use the info hostfs command to give an argument to a task, and exploit the target file on the host. Something like a task which would do:

void openFile
    (
    char *      path    /* Target path to write to */
    )
    {
    FILE *      tgtstream       = open (path, "w");

    fprintf (tgtstream, "From target task !\n");
    fclose (tgtstream);
    }

Getting the file content from the host could look like:

def taskSpawn(entrypoint, *args):
    # Create task arguments list
    arguments = ''
    for arg in args:
        arguments += ' ' + str(arg)

    # Spawn the task, and get its ID and name.

    task = gdb.execute('thread create ' + entrypoint + arguments, False, True)
    taskID = task.split('id = ')[1].split(',')[0]
    taskName = task.split('name = ')[1].split(',')[0]

    # Wait for the task to be spawned and stopped before issuing any further
    # command

    waitfor = (entrypoint + ' (', ' at ')
    matchesall = False
    timeout = 30
    waits = 0

    # The task is stopped when the "<entrypoint> (.*) at " appears in the
    # thread info of our task (meaning it is stopped at <entrypoint>

    while not matchesall:
        matchesall = True
        cmdix = gdb.STDOUT.tell()
        gdb.execute('info thread')
        output = gdb.STDOUT.getvalue()[cmdix:]
        for match in waitfor:
            if not match in output:
                matchesall = False
        if not matchesall:
            time.sleep(1)
            waits += 1

        # Do not wait forever ...

        if waits > timeout:
            gdb.execute('target disconnect')
            vxsimStop()
            raise Exception('Thread never created')

    # XXX: fle : to emulate the sp() behavior, we should add the following line
    # here:
    #
    # gdb.execute('continue &')
    #
    # This would require changes in the code handling this task though.

    return (taskID.strip(), taskName.strip())

def taskWait(taskID, taskName):
    """Wait for a task to exit."""
    cmdix = gdb.STDOUT.tell()
    taskexited = '[Thread ' + taskID + ' exited]'
    while True:
        # First, check that the task is stopped. If it is, send a "continue"
        taskstates = gdb.execute('info threads', False, True)
        for state in taskstates.split('\n'):
            if taskID in state and taskName in state and ' at ' in state:
                gdb.execute('continue')
        # Check if the output contains [Thread <taskID> exited]. If not, wait
        # for a second.
        cmdoutput = gdb.STDOUT.getvalue()[cmdix:]

        if taskexited in cmdoutput:
            return

        time.sleep(1)

...
tgtfile = '/wrdbg/root/C/tmp/out.txt'
taskID, taskName = taskSpawn('openFile', tgtfile)
gdb.execute('continue', False, True)

print 'Waiting for ' + taskName + ' to end ...'

taskWait(taskID, taskName)

# Now read the file on host

hostpath = 'C:\tmp\out.txt'

with open(hostpath, 'r') as f:
    print f.read()
...

Task deletion

The use of hostShell’s td has been replace by the thread kill – Kill a thread gdb command.

In confirm mode, the command asks for confirmation when trying to kill a running task. In that case, it is safer to define a python function to handle that gracefully:

def taskKill(taskID):
    # Kill the task. If the task was running or pending, the string
    # 'Kill the thread being debugged?' appears in the result. If the
    # string '[answered y; input not from terminal]' does not also
    # appear in the result, the 'y' command (for 'yes') is awaited.

    cmdix = gdb.STDOUT.tell()

    gdb.execute('thread kill ' + taskID)
    cmdoutput = gdb.STDOUT.getvalue()[cmdix:]

    if 'Kill the thread being debugged?' in cmdoutput and \
       '[answered y; input not from terminal]' not in cmdoutput:
        gdb.execute('y')

Breakpoints

While using C’s b and bd with the hostShell, breakpoints are handled using break and delete breakpoints – Delete some breakpoints or auto-display expressions GDB commands.

Set a breakpoint

Using break allows to set a breakpoint at a given location. The command returns a string like Breakpoint 1 at 0x1092d858: file ../../src/kernel/phil.c, line 487. from which the breakpoint ID may be extracted like:

symbol = 'main'
bpDesc = gdb.execute('break ' + symbol, False, True)
bpID = bpDesc.split(' ')[1]

A python function may be written to do that:

def bpAdd(symbol):
    bp = gdb.execute('break ' + symbol, False, True)
    # Extract the breakpoint id from returned breakpoint string, it should be
    # the second word of the result.

    bpID = bp.split(' ')[1]

    return bpID

Note

Using tbreak instead of break creates a temporary breakpoint removed as soon as it is hit.

Remove a breakpoint

A breakpoint created by the break command may be removed by the delete breakpoints – Delete some breakpoints or auto-display expressions command.

def bpAdd(symbol):
    bp = gdb.execute('break ' + symbol, False, True)
    # Extract the breakpoint id from returned breakpoint string, it
    # should be the second word of the result.

    bpID = bp.split(' ')[1]

    return bpID

...
symbol = 'main'
bpID = bpAdd(symbol)
...
gdb.execute('delete breakpoints ' bpID)

Remove all breakpoints

Removing all the breakpoints is done by using the delete breakpoints – Delete some breakpoints or auto-display expressions command with no breakpoint ID.

def bpAdd(symbol):
    bp = gdb.execute('break ' + symbol, False, True)
    # Extract the breakpoint id from returned breakpoint string, it
    # should be the second word of the result.

    bpID = bp.split(' ')[1]

    return bpID

...
symbol = 'main'
symbol2 = 'main2'
bpID1 = bpAdd(symbol)
bpID1 = bpAdd(symbol2)
...
gdb.execute('delete breakpoints')

In confirm mode, the command asks for confirmation when trying to delete all breakpoints. In that case, it is safer to define a python function to handle that gracefully:

def bpRemove(bpIDs=[]):
    # If bpIDs is a basetring, assume it is only one breakpoint ID, else,
    # create a string of all breakpoint IDs.
    if bpIDs and isinstance(bpIDs, basestring):
        ids = bpIDs
    elif bpIDs:
        ids = ' '.join(bpIDs)
    else:
        ids = ''
    cmdix = gdb.STDOUT.tell()
    gdb.execute('delete breakpoints ' + ids)
    cmdoutput = gdb.STDOUT.getvalue()[cmdix:]
    if 'Delete all breakpoints?' in cmdoutput and \
       '[answered y; input not from terminal]' not in cmdoutput:
        gdb.execute('y')

Path mapping

When objects have been compiled on a given path, but that path is not available from the host debugging the object, path mapping should be used.

For example, if mytest.c has been compiled in /tmp/test, but is debugged on a Windows host where mytest.c resides in C:\test, a path mapping must be created to access the source file.

gdb.execute('pathmap add /tmp/text C:\\test')

Note

The pathmap add command may be called only after the target connect – Connect to a target machine.

Logging

The target server connection allowed to turn on WTX or WDB loggers. Using wrdbg the equivalent is the TCF agent logger. To collect the target->tools communication, two possible ways:

  • use the -logverbosity option of the target connect – Connect to a target machine command. In that case, all TCF logs are written in the directory specified by the -logdir option. For vxworks7 platform, setting the log verbosity to 2 enables maximum logging.
  • use the various set agentlog commands.
import gdb
import windriver.utils.utils as wrutils
agentlog = wrutils.fullpath(('~', 'tmp', 'agent-log.txt'))
gdb.execute('set agentlog file ' + agentlog)

When done, you may turn on or off the debug log with:

...
gdb.execute('set agentlog save on')
...
gdb.execute('set agentlog save off')
...

Code example

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 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
# task.py - WindRiver gdb interface example for vxworks7 connection.

# Copyright (c) 2014-2015 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.

import argparse
import gdb
import os
import random
import subprocess
import sys
import time

import windriver.utils.utils as wrutils

_vxsimStarted = False


def bpAdd(symbol):
    bp = gdb.execute('break ' + symbol, False, True)
    # Extract the breakpoint id from returned breakpoint string, it should be
    # the second word of the result.

    bpID = bp.split(' ')[1]

    return bpID


def bpRemove(bpIDs=[]):
    # If bpIDs is a basetring, assume it is only one breakpoint ID, else,
    # create a string of all breakpoint IDs.
    if bpIDs and isinstance(bpIDs, str):
        ids = bpIDs
    elif bpIDs:
        ids = ' '.join(bpIDs)
    else:
        ids = ''
    cmdix = gdb.STDOUT.tell()  # @UndefinedVariable
    gdb.execute('delete breakpoints ' + ids)
    cmdoutput = gdb.STDOUT.getvalue()[cmdix:]  # @UndefinedVariable
    if 'Delete all breakpoints?' in cmdoutput and \
       '[answered y; input not from terminal]' not in cmdoutput:
        gdb.execute('y')


def taskSpawn(entrypoint, *args):
    # Create task arguments list
    arguments = ''
    for arg in args:
        arguments += ' ' + str(arg)

    # Spawn the task, and get its ID and name.

    task = gdb.execute('thread create ' + entrypoint + arguments, False, True)
    taskID = task.split('id = ')[1].split(',')[0]
    taskName = task.split('name = ')[1].split(',')[0]

    # Wait for the task to be spawned and stopped before issuing any further
    # command

    waitfor = (entrypoint + ' (', ' at ')
    matchesall = False
    timeout = 30
    waits = 0

    # The task is stopped when the "<entrypoint> (.*) at " appears in the
    # thread info of our task (meaning it is stopped at <entrypoint>

    while not matchesall:
        matchesall = True
        cmdix = gdb.STDOUT.tell()  # @UndefinedVariable
        gdb.execute('info threads')
        output = gdb.STDOUT.getvalue()[cmdix:]  # @UndefinedVariable
        for match in waitfor:
            if match not in output:
                matchesall = False
        if not matchesall:
            time.sleep(1)
            waits += 1

        # Do not wait forever ...

        if waits > timeout:
            gdb.execute('target disconnect')
            vxsimStop()
            raise Exception('Thread never created')

    # XXX: fle : to emulate the sp() behavior, we should add the following line
    # here:
    #
    # gdb.execute('continue &')
    #
    # This would require changes in the code handling this task though.

    return (taskID.strip(), taskName.strip())


def taskWait(taskID, taskName):
    """Wait for a task to exit."""
    cmdix = gdb.STDOUT.tell()  # @UndefinedVariable
    taskexited = '[Thread ' + taskID + ' exited]'
    while True:
        # First, check that the task is stopped. If it is, send a "continue"
        taskstates = gdb.execute('info threads', False, True)
        for state in taskstates.split('\n'):
            if taskID in state and taskName in state and ' at ' in state:
                gdb.execute('continue')
        # Check if the output contains [Thread <taskID> exited]. If not, wait
        # for a second.
        cmdoutput = gdb.STDOUT.getvalue()[cmdix:]  # @UndefinedVariable

        if taskexited in cmdoutput:
            return

        time.sleep(1)


def taskKill(taskID):
    # Kill the task. If the task was running or pending, the string
    # 'Kill the thread being debugged?' appears in the result. If the string
    # '[answered y; input not from terminal]' does not also appear in the
    # result, the 'y' command (for 'yes') is awaited.

    cmdix = gdb.STDOUT.tell()  # @UndefinedVariable

    gdb.execute('thread kill ' + taskID)
    cmdoutput = gdb.STDOUT.getvalue()[cmdix:]  # @UndefinedVariable

    if 'Kill the thread being debugged?' in cmdoutput and \
       '[answered y; input not from terminal]' not in cmdoutput:
        gdb.execute('y')


def vxsimStart(kernel, logdir):
    """Start vxsim 0 with given kernel."""
    global _vxsimStarted
    tcfport = 5000 + int(random.random() * 60000)
    url = 'vxworks7:127.0.0.1:' + str(tcfport)
    logfile = wrutils.fullpath((logdir, 'vxsim-log.txt'))
    # Build vxsim command line
    cmd = ['vxsim', '-p', '0', '-d', 'simnet_nat', '-add_nat_redir',
           'tcp:1534:' + str(tcfport), '-f', kernel, '-lc', '-l', logfile]
    subprocess.Popen(cmd)
    # Wait a bit for the simulator to be started
    time.sleep(5)
    _vxsimStarted = True
    return url


def vxsimStop():
    """Stop the vxsim 0."""
    # Build vxsim kill command line. Assume vxsim processor is 0.
    global _vxsimStarted
    if _vxsimStarted:
        cmd = ['vxsim', '-kill', '0']
        subprocess.Popen(cmd)

# ------------------------- user defined variables -------------------------- #

bpsymbol = 'level4'
errfile = 'vxworks_undef_sym.txt'
module = 'bklevel.o'
taskentry = 'bklevel'

# Source path mappings (if needed)

# pmapsrc = <path to source at compile time>
pmapsrc = None
# pmapdest = <path to source at run time>
pmapdest = None

# ----------------------------- Parse arguments ----------------------------- #

parser = argparse.ArgumentParser(description='Task sample',)

parser.add_argument('-kernel', required=True, help='Target kernel path.')
parser.add_argument('-logdir', default='~/tmp/samples', help='Log directory.')
parser.add_argument('-objdir', required=True, help='Objects directory.')
parser.add_argument('agentid', nargs=argparse.REMAINDER,
                    help='Target agent URL (ID). If argument is not '
                         'provided, a vxsim is started using KERNEL')

# Print help message if user forgot to give arguments

if len(sys.argv) == 1:
    parser.parse_args(['-h'])

parsed = parser.parse_args(sys.argv[1:])

kernel = wrutils.fullpath(parsed.kernel)
logdir = wrutils.fullpath(parsed.logdir)
objdir = wrutils.fullpath(parsed.objdir)
agentlog = wrutils.fullpath((logdir, 'agent-log.txt'))

if not parsed.agentid:
    url = vxsimStart(kernel, logdir)
else:
    url = parsed.agentid[0]

# --------------------------------------------------------------------------- #

if not os.path.exists(logdir):
    os.makedirs(logdir)

os.chdir(objdir)

try:
    # Connect to the target

    gdb.execute('set agentlog file ' + agentlog)
    gdb.execute('target connect ' + url + ' -kernel ' + kernel + ' -logdir ' +
                logdir)
    # gdb.execute('set agentlog save on')

    if pmapsrc and pmapdest:
        gdb.execute('pathmap add ' + pmapsrc + ' ' + pmapdest)
        gdb.execute('info pathmap')

    # Attach to the kernel

    gdb.execute('attach Kernel')

    # Check that module does not have undefined symbols.

    loadResult = gdb.execute('module load ' + module, False, True)
    if 'Error text: S_loadLib_UNDEFINED_REFERENCES' in loadResult:
        undefs = gdb.execute('info modules ' + module + ' -print undef', False,
                             True)
        with open(errfile, 'w') as f:
            f.write(undefs)
        gdb.execute('module unload ' + module)
        # Detach from Kernel
        gdb.execute('detach')
        # Disconnect from the target
        gdb.execute('target disconnect')
        quit()

    gdb.execute('module load ' + module)

    bpID = bpAdd(bpsymbol)

    taskID, taskName = taskSpawn(taskentry)

    # Run this task up until we hit our breakpoint

    while True:
        hit = gdb.execute('continue', False, True)
        if 'Breakpoint ' + bpID in hit:
            break

    # Remove the breakpoint

    bpRemove(bpID)
    taskKill(taskID)

    gdb.execute('module unload ' + module)

    # Detach from Kernel

    gdb.execute('detach')

    # Disconnect from the target

    gdb.execute('target disconnect')
except Exception as e:
    sys.stderr.write('*** ERROR ***\n' + str(e) + '\n')

# Stop the target if needed

vxsimStop()

# Save outputs to log file.

consolelog = wrutils.fullpath((logdir, 'gdb-console.txt'))

with open(consolelog, 'w') as f:
    f.write(gdb.STDOUT.getvalue())  # @UndefinedVariable
    print('Logs saved in ' + consolelog)

quit()