By Jakob Engblom
How do you actually connect an integrated development environment or a debugger to a target system? This question is more complicated than it might seem to the uninitiated outsider. Traditionally, a range of protocols have been used to connect the development host to a target, most of them vendor-proprietary, and often specialized for a particular purpose (such as debug, downloading code, or uploading profile data). However, there is a better alternative available today, the open-source TCF, Target Connection Framework. The TCF is much more than a debugger connection. Based on decades of industry experience as to what you actually need to do with a target, TCF combines everything on a single connection: analysis, testing, configuration, control, provisioning, inspection, tracing, debugging, and anything else you might think of. Simics is making good use of TCF to simplify the connection between the Simics runtime engine and the Simics Eclipse-based GUI. In this blog post, I will go into how TCF is used with Simics and what it has enabled us to do that would have been very difficult without the TCF.
First, we need a little background on TCF itself. The TCF is a project in Eclipse, with contributions from Wind River, Xilinx, and others. The TCF protocol is service-based, and each target exposes the services that it supports to the host. The host can then activate the appropriate functionality, based on what the target reports. In this way, there is no need for the host to know very much about the target a priori, as all its capabilites are reported dynamically after a connection has been setup.
Anyway, on to Simics and TCF. Given what TCF is, how did we use it with Simics?
When using Simics from Eclipse, there are actually two processes running. There is the Simics-common process that contains the actual Simics simulator and the Eclipse process that contains the GUI. These two processes obviously need to be connected to each to allow the GUI to display the state of the simulation and control it.
As shown in the picture above, this connection is achieved using the TCF protocol over a TCP/IP connection. Note that there always just a single connection, regardless of what you are doing with Simics and how many target machines the Simics session contains.
The screenshot above shows some of the basic Simics views in Eclipse, for a simple setup containing four target machines (see a previous blog post for more on this setup, and a video of the debugger in action). All the views get their content over the single TCF connection to Simics, for all the target systems. This includes not just basic functions like starting and stopping the target, but also the system panel view showing the front panels of the targets, the device register viewer, the target information view, and the interactive Simics command-line (not shown above). When we attach a debugger to this setup, the same TCF connection is used to also carry the debug session for the entire target system (all four target machines).
Without TCF, we would have needed many separate connections between Simics and Eclipse. Typically, one connection to control the Simics simulation, and then one debug connection to each target system or individual OS on a target system. It would look something like this:
Each of these connections would be established separately, and they would need to be coordinated both in Simics and in Eclipse to avoid interfering with each other. For example, even basic run control gets horribly complex in a situation where you have multiple debuggers trying to interact with the same target. Gdb, for example, has a very hard time with targets that start and stop under the control of other debuggers or control mechanisms – it is basically designed to assume that there is only one controlling debugger, not a whole bunch of equals. With TCF, run control is one service shared across all the targets (Simics starts and stops them all as a unit, remember?) and functions in the Simics GUI. It is even possible to connect a second Eclipse debugger to the same Simics without any issue, since TCF makes sure that the target system is the authority on its state. A funny effect of this is that anything typed on the Simics command-line in the first Eclipse is mirrored in the second Eclipse, since all state changes are mirrored via the Simics TCF server.
Thanks to TCF, we have been able to build a system-level debugger for Simics that integrates perfectly with the non-debugger features of Simics. When you reverse the simulation from the Simics control view, the debugger understands what happens. When the debugger stops the run on a breakpoint, the Simics controls update and the Simics command-line notes that the simulation is stopped. When a Simics script detects an erroneous state in the target and stops the simulation, the debugger immediately shows the location in the code where execution has stopped. If a Simics advanced breakpoint (such as breaking on log messages) stops the execution, the debugger kicks in and shows where in the code the breakpoint hit.
When used for debugging, TCF brings some unique benefits. TCF is capable of managing an arbitrarily complex target system over a single connection. There is no requirement that the target is homogeneous or uses a single OS image. With Simics as the backend, the debug session can cover multiple boards, each with multiple processors, potentially running multiple OSes on each board. Such a setup was used for the demo referenced above, and you can see it in action here. With other debugger protocols, each board would have required a separate connection, and a separate debugger would have been needed to connect to each board since they tend to be target-architecture-specific. With TCF, system level debug across a four-machine networked setup featuring two different architectures and two different operating systems was trivial to realize.
Another important aspect of TCF is that it is designed to allow services to run anywhere along a daisy chain of units from the target to the final debugger. With Simics, this is not used, but when using a hardware setup, it is possible to run a simple agent on a target and add features like OS awareness and symbol lookup in a value-add process along the way. It would look like this, and note that we can also carry the data needed to drive analysis tools like performance profilers and event tracers over the same connection:
Still, the modularity of TCF is used with Simics. Thanks to the location-neutrality of TCF services, the debugger smarts are actually located within the Simics process. They might just as well have been run in their own process, but by putting them inside of Simics, the debugger functionality is made available to Simics scripts. This means that Simics command-line scripts, Python scripts, and arbitrary Simics modules in Python, C, or C++ can all make use of the debugger functions like OS awareness and symbol lookup. The debugger is also available from the Simics command-line, giving users a very powerful debug system that has direct access to all parts of the target. This debug system is automatically in sync with the GUI debugger, thanks to TCF, allowing a seamless mix of classic inside-of-Simics debug and debug from the Eclipse GUI.
The performance of the debugger is greatly improved by being located within Simics, as the evaluation of conditional breakpoints and other features that require interaction between debugger and target is done locally at very high speed. This is in contrast to protocols like gdb-serial where the separate debugger process has to be invoked to evaluate conditional breakpoints and do other debug tasks, leading to much higher latency and lower performance. The debugger is also able to take advantage of Simics OS awareness features like detecting when a process or thread is being scheduled by the OS. This would simply not be possible with a remote debugger, as the latency of communication would render it totally useless.
To summarize, TCF has allowed us to build a very robust and powerful integrated debugger and GUI for Simics, with a single consistent notion of the state of the target simulation and a single connection between the simulation backend and the GUI frontend.
For additional information from Wind River, visit us on Facebook.