Debugging with SCons and Visual Studio

Current Repository Revision: 14

Code highlight key
Grey: Code unchanged
Red: Code removed
Green: Code added
Blue: Code not shown

We can now create Visual Studio projects for our example code and use them to help when debugging using the Visual Studio debugger.

Before we can start debugging, however, we need to fix the debug variant build so that it can be built with the Visual Studio compiler, both at the command line and in the IDE.

Fixing the debug build for Visual Studio command line

If you try to build a debug variant of the code, using Visual Studio as the compiler, with the command:

scons -Q debug=true

the following error will appear:

scons: *** Multiple ways to build the same target were
specified for: build\debug\mi_debug.pdb (from ['bar_test.obj']
and from ['raw_test.obj'])

The error occurs because the PDB variable has to be set for SCons to perform a debug build when using Microsoft Visual Studio. This is set in the SConstruct file (to the value mi_debug.pdb) and never changed, so every executable tries to use this as its Program Database file. This causes a clash in targets.

To solve this we simply need to set the PDB variable for each executable if debug=true is specified on the command line. This can be done inside the MI_BuildTestsAndExecute function inside mi_scons.py (at the moment this is the only function we have which builds executables):

nxttst = os.path.basename(nxttst)
if env['debug']:
      pdb_name = os.path.splitext(nxttst)[0]+'.pdb'
      env.Replace(PDB = env['currbdir']+env['pathsep']+pdb_name)
trgt = env.Program(nxttst)

The debug variant of the code can now be built successfully, and the correct .pdb files are created – essential for debugging with Visual Studio.

Fixing the debug build for Visual Studio IDE

The above code fixes the debug variant so it can be built on the command line when Visual Studio is your compiler, but if you build the Visual Studio projects for the debug build with:

scons -Q debug=true msvs=true

and load a resulting project into the Visual Studio IDE and select Build you will get an error similar to:

scons: *** Do not know how to make target
          `build\debug\foobar\lib\foobar.lib'.  Stop.

When we build on the command line we specify the option debug=true which does a lot of setting up (including setting the build directory to build/debug). So if we want to be able to build the debug variant in Visual Studio (which we may during a debugging session) we need to be able to pass the debug=true switch to SCons.

We add all our switches by appending them to the end of the MSVSSCONSFLAGS variable in our environment. This is done in the SConstruct file.

# We need to set up MSVS command lines before changing bdir
# We can add our flags to the MSVSSCONSFLAGS variable
# We just add them all, whether default or not
# msvs, runtests, mingw and vs2005 don't apply for obvious reasons.
if env['msvs']:
  env.Append(MSVSSCONSFLAGS = ' bdir='+env['bdir'])
  env.Append(MSVSSCONSFLAGS = ' sdir='+env['sdir'])
  env.Append(MSVSSCONSFLAGS = ' debug='+str(env['debug']))
  env.Append(MSVSSCONSFLAGS = ' forgive='+str(env['forgive']))

Now you can load the debug project files into your Visual Studio and build them.

Multiple build variants in one project file

Rather than have to specify debug=true at the command line, it would be nice if just specifying msvs=true would create both release and debug variants in a single project file, and put them in a directory named msvs instead of in the debug and release directories. According to the SCons manual:

variant: The name of this particular variant. For Visual Studio 7 projects, this can also be a list of variant names. These are typically things like “Debug” or “Release”, but really can be anything you want.

Multiple calls to MSVSProject with different variants are allowed; all variants will be added to the project file with their appropriate build targets and sources.

It seems the  first part of this (using a list of variants) isn’t much help to us, because we need to set the MSVSSCONSFLAGS variable to be different for the release and debug builds (debug=true for debug and debug=false for release). So instead we need to call the same target twice with different flags set. When trying this we get the error:

scons: warning: Two different environments were specified for
       target bar_test.vcproj, but they appear to have the same
       action: GenerateProject(target, source, env)
...

scons: *** Multiple ways to build the same target were specified
       for: bar_test.vcproj ...

I think this is because we try to change the environment (the MSVSSCONSFLAGS variable) between calls. Whatever the reason, this doesn’t work, and I have to go back to the part I said earlier ‘…isn’t much help to us…’.

Now, what follows is an ugly hack. I’m sure (hope!) that there is a proper way to do this, and someone will point it out to me. In the meantime, I made the following changes. Firstly I remove the debug reference from the MSVSSCONSFLAGS variable we had just added (as msvs=true will now build both variants) in the SConstruct file.

# We need to set up MSVS command lines before changing bdir
# We can add our flags to the MSVSSCONSFLAGS variable
# We just add them all on, whether default or not
# msvs, runtests, mingw and vs2005 don't apply for obvious reasons.
if env['msvs']:
  env.Append(MSVSSCONSFLAGS = ' bdir='+env['bdir'])
  env.Append(MSVSSCONSFLAGS = ' sdir='+env['sdir'])
  env.Append(MSVSSCONSFLAGS = ' debug='+str(env['debug']))
  env.Append(MSVSSCONSFLAGS = ' forgive='+str(env['forgive']))

We also want to build the projects in the directory msvs, so we need to make sure we set the build directory to that if msvs=true. This is also in the SConstruct file.

if env['msvs']:
  env.Append(bdir = env['pathsep']+'msvs')
elif env['debug']:
  env.Append(CCFLAGS = dbgf)
  env.Append(bdir = env['pathsep']+'debug')

  # MSVC is a special case where we have to set the PDB
  # variable to add debug flags
  if cmplr=='cl': env.Replace(PDB='%s/mi_debug.pdb' % env['bdir'])
else:
  env.Append(bdir = env['pathsep']+'release')

Then we can add some code to mi_scons.py which will set up and build both variants. The horrible hack is that we add the build=true switch to the build target, so it is used in the command line. To do this we must remember that the build target is usually surrounded in quotes, so we have to add some quotes to make the command correct. We also make sure the directory name is correct for the build targets.

# Create the variant build targets with a nasty hack for the debug!
# We add the debug=true switch so it is used in the command! We need
# to use some carefully placed quotes for this to work
relbldtar = string.replace(bldtar, 'msvs', 'release', 1)
dbgbldtar = 'debug=true" "'+string.replace(bldtar, 'msvs', 'debug', 1)

if env['debug']: vrnt = 'Debug'
else: vrnt = 'Release'

outmsvs = env.MSVSProject(target = projname,
                          srcs = sources,
                          incs = heads,
                          buildtarget = bldtar,
                          buildtarget = [relbldtar,dbgbldtar],
                          variant = vrnt,
                          variant = ['Release', 'Debug'],
                          runfile = rnfl,
                          auto_build_solution=0)

This code works for creating multiple variants in a single Microsoft Visual Studio Project, but any executables you create won’t run. This is because the runfile set in the project is just the name of the executable. This was fine when the projects were in the release or debug directories, but now Visual Studio looks for the executables in the msvs directory. To solve this we need to specify the runfile with the full path to the executable, and pass a list (one for each variant) to the MSVSProject builder.

# Whatever the current build target is, we need to calculate the
# full path to the final build location. This is what we need
# to get Visual Studio to run built programs, so use that
# as the run file
rnfl = bldtar
rnfl = env['currbdir']+env['pathsep']+bldtar
dbgrnfl = string.replace(rnfl, 'msvs', 'debug', 1)
relrnfl = string.replace(rnfl, 'msvs', 'release', 1)

# Other setup code
...

outmsvs = env.MSVSProject(target = projname,
                          srcs = sources,
                          incs = heads,
                          buildtarget = [relbldtar,dbgbldtar],
                          variant = ['Release', 'Debug'],
                          runfile = rnfl,
                          runfile = [relrnfl, dbgrnfl],
                          auto_build_solution=0)

Now you can run the code, but the dialog will still appear telling you the executable is out of date when it isn’t, as mentioned in the previous post. This dialog can be useful in other circumstances, so I’m hesitant about ticking the ‘Do not show this dialog again’ box in case it disappears altogether!

Debugging SCons builds with Visual Studio

Now all the projects are set up as we want them. All the Visual Studio rubbish ends up in the msvs directory tree (except for the pdb and incremental linker – ilk – files), and we have both our debug and release variants in single project files, one for each library and one for each executable.

The projects are useful because they enable us to debug the code effectively in Visual Studio. By creating a solution and loading all (or a subset) of the projects into it you allow Visual Studio’s Intellisense to browse through the code. Then you can use the features of Visual Studio to track down problems.

You can use the code definition window to see where variables, functions and classes are defined, and the code that makes up a class or function. The call graph window enables you to see what calls a particular function, or what functions it calls.  Then you can use the debugger, place breakpoints, edit and rebuild code, even add new files (our SConscript files look for all .c, .cxx and .h files rather than specific names, so there’s no need to edit the SConscript when adding new code files). You can edit the SConscript files inside Visual Studio if, for example, you add a new directory.

I will use Visual Studio projects for debugging SCons based code, but not generally for creating and editing code, or building and testing. This is because, for each project you need to build, Visual Studio needs to call SCons, and all the set up code is repeated, making it less efficient. I prefer just to compile from the command line – it’s what SCons is about. SCons is a full build system – not just a configuration system for other builders such as make or the Visual Studio environment.

Debugging SCons build in Visual Studio without the projects

Of course, you don’t need to use the project files to debug SCons built executables with Visual Studio.

You can debug any executable by building a debug version of the code at the command line (for example, use scons -Q debug=true to build our example code). Open Visual Studio, and create a new, general, empty project. Choose to insert an existing project, and in the dialog select Executable files as the file type. Then browse to the executable you want to debug, and load it in. Set it as your start up project, and then you can either open a file containing the code you want to debug, place a breakpoint and start debugging (F5), or start at the beginning of the executable with Step Into from the Debug menu (or F11).

You can also debug any executable in Visual Studio by attaching the debugger to a running process. This isn’t much use given the current example code as the processes will have ended before we can attach the debugger, but it can be useful in longer processes where you don’t see the problem when you start it in the debugger (Hint: if this is the case it often points to memory issues).

Again, build the debug version of the code on the command line, run the program, open Visual Studio and select Attach To Process… from the Tools menu. Then you can either open a file containing code you know the program is going to get to soon and put a breakpoint in it, or you can break the code (using the pause symbol), look at the stack trace and select a function you recognise as yours (rather than the deep, dark Microsoft ‘write a character to the screen’ function that I always end up breaking into!). Selecting the familiar function in the stack trace should open up the relevant file. Then place a breakpoint and away you go!


The code updates discussed in this post are committed as revision 14 in the repository.

Hopefully, that’s the Visual Studio projects finished with for the time being. Now we can get on with adding more code to test other features of SCons and see how well it can build our code.

Leave a Reply

You must be logged in to post a comment.