Skip to content

Investigate Debugging story #68

Closed
glennblock opened this Issue Mar 7, 2013 · 29 comments

5 participants

@glennblock
Scriptcs member

One of the things people are asking is how they can debug Rosyln sripts. Today Roslyn doesn't support debugging, though it does support compiling to a dll.

So, the question is can one figure out how using that you could attach to a process using VS to debug.

I am not sure it's doable, as Roslyn may not include PDBs. Anyway, if someone wants to run with this to see if it's possible, we'd love it!

We're not asking for a PR (yet) we're asking for a proof of concept.

@dschenkelman
Scriptcs member

I can give this a shot, as long as there is no need to have it ASAP as I don't know how much time I'll have to work on this.

@glennblock
Scriptcs member

@dschenkelman no rush.

@dschenkelman
Scriptcs member

I have created a small spike that takes a .csx file (with a main method as entry point) and creates a console app .exe and a related .pdb.

I have uploaded it to my repo fork, and created a readme that explains the most important things. Check it out here.

The spike shows that a .pdb file can be correclty created (assuming tweaks are performed to scriptcs code gen) and debugged (tried it using mdbg).

If you think the spike is useful, we can have a discussion about where to go next.

@glennblock
Scriptcs member
@glennblock
Scriptcs member
@glennblock
Scriptcs member
@dschenkelman
Scriptcs member

I'll spend a bit of time checking out possible approaches to get this done although I don't know if it will be possible. Perhaps I'm getting ahead, but what I believe is the bad part of this approach is generating code that can be compiled using the SyntaxTree approach from the *.csx files.

@dschenkelman
Scriptcs member

After a bit of time looking for an approach to get the compilation from the .csx without using the SyntaxTree, using the .csx script directly, I found a possible way.

var scriptEngine = new ScriptEngine();
scriptEngine.AddReference("System");
scriptEngine.AddReference("System.Core");
var session = scriptEngine.CreateSession();

Submission<object> s = session.CompileSubmission<object>(code);

var temp = (Compilation)s.Compilation;
// using ConsoleApplication is just a spike, the default is .dll
var compilation = temp.WithOptions(temp.Options.WithOutputKind(OutputKind.ConsoleApplication));

CommonEmitResult result;

using (FileStream outputStream = new FileStream(outputPath, FileMode.OpenOrCreate))
using (FileStream pdbStream = new FileStream(pdbPath, FileMode.OpenOrCreate))
{
    result = compilation.Emit(outputStream, outputName, pdbPath, pdbStream);
}

The .exe and .pdb are generated:
generatedCode

But when the .exe is run, the following error is thrown:

Unhandled Exception: System.MethodAccessException: Main method for type 'Submiss
ion#0' has invalid signature.

Perhaps the correct approach would be to compile to a .dll an then invoke the method, but it seems that this would allow us to keep host objects by providing the session to the generated .dll.

@glennblock
Scriptcs member
@dschenkelman
Scriptcs member

Just performed one to see if the script runs using this code:

var scriptEngine = new ScriptEngine();
scriptEngine.AddReference("System");
scriptEngine.AddReference("System.Core");
var session = scriptEngine.CreateSession();

Submission<string> s = session.CompileSubmission<string>(code);

var compilation = (Compilation)s.Compilation;

// need to add assemblies
CommonEmitResult result;

using (FileStream outputStream = new FileStream(outputPath, FileMode.OpenOrCreate))
using (FileStream pdbStream = new FileStream(pdbPath, FileMode.OpenOrCreate))
{
    result = compilation.Emit(outputStream, outputName, pdbPath, pdbStream);
}

if (result.Success)
{
    Console.WriteLine("Compilation successful");
    Console.WriteLine(string.Format("Output .exe at {0}", outputPath));
    Console.WriteLine(string.Format("Output .pdb at {0}", pdbPath));

    var assembly = Assembly.LoadFrom(outputName);
    var type = assembly.GetType("Submission#0");
    var method = type.GetMethod("<Factory>", BindingFlags.Static | BindingFlags.Public);
    method.Invoke(null, new[]{ session });
}

This produced the expected output:

IMPORTANT
================================================
In this spike the .csx file must only make use of classes in mscorlib, System an
d System.Core assemblies.
================================================

Compilation successful
Output .exe at C:\Users\Damian\Documents\GitHub\scriptcs\spikes\DebugSymbols\Deb
ugSymbols\bin\Debug\test.dll
Output .pdb at C:\Users\Damian\Documents\GitHub\scriptcs\spikes\DebugSymbols\Deb
ugSymbols\bin\Debug\test.pdb
Attach and the press ENTER to start

3

My next step will be to either attach to the running instance with mdbg (if possible), or create an .exe that just invokes the script through reflection assuming that the .dll is in the same dir.

@filipw
Scriptcs member
filipw commented Mar 8, 2013

Nice!

@dschenkelman
Scriptcs member

Been working on attaching to a running console app (which simulates scriptcs.exe) from either VS or mdbg.

  • [success] Able to attach from mdbg (more on that below)
  • [failed] Could not attach from VS.

VS issue

As there is no 1 to 1 mapping between source and compiled code, so the .pdb does not have a related file and VS cannot attached to a particular file to be debugged (any ideas to work around this?). For example, this source:

using System;
using System.Diagnostics;

Debugger.Break();

Console.WriteLine("Testing");

public class Writer
{
    public void Write(string s)
    {
        Console.WriteLine(s);
    }
}

int a = 1;
int b = 2;

int c = a + b;
new Writer().Write(c.ToString());
Console.ReadLine

Is compiled into:

using Microsoft.CSharp.RuntimeHelpers;
using Roslyn.Scripting;
using System;
using System.Diagnostics;
public sealed class Submission#0
{
    public class Writer
    {
        public void Write(string s)
        {
            Console.WriteLine(s);
        }
    }
    public int a;
    public int b;
    public int c;
    public Submission#0(Session session, ref object submissionResult)
    {
        SessionHelpers.SetSubmission(session, 0, this);
        Debugger.Break();
        Console.WriteLine("Testing");
        this.a = 1;
        this.b = 2;
        this.c = checked(this.a + this.b);
        new Submission#0.Writer().Write(this.c.ToString());
        submissionResult = Console.ReadLine();
    }
    public static object <Factory>(Session session)
    {
        object result;
        new Submission#0(session, ref result);
        return result;
    }
}

Using mdbg

I have been able to run a console app that compiles the .csx into a .dll with its related .pdb and attach to it using mdbg.exe that is located at C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\NETFX 4.0 Tools (the location is important because there are might be others at C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin that are installed with the Windows SDK that did not work).
The steps to attach are:
1. Run the application (simulating scripcs.exe) which prompts the user to attach the debugger before continuing.
2. Attach to the console application using mdbg by running attach {pid}.
3. Press ENTER in the console app.
4. Run go in mdbg, which is like VS F5. The first breakpoint will be hit.

The following figures show a simple debugging session (using the source code provided above). The line numbers are the same as the ones in the original source code file, but there is no out-of-the-box way to check the source code from mdbg because .pdb does not point to the source file:
debugSession
Debug session

execution
Execution

Possible improvements

The aforementioned approach does work but is definitely a "poor man's debugger". Some of the things I have considered to improve this are (in no particular order):
1. Tooling to map source code to the debugger's current line, which will allow users to directly see where they are without having both things side by side.
2. Support for some sort of #break directive in .csx files so users don't have to include System.Diagnostics and which is only taken into account when scriptcs is configured to debug (something like scriptcs -d?).
3. Immediate window debugging support, so you could step through the code using mdbg but have the ability to watch variables in a simpler way (would having access to the Session and using its execute method work?).

Nevertheless, there are some related items that you might probably would like to see addressed before 1 and 3, such as:

Proposal

For the time being (unless you would like to me help in any of the above), I could:
1. Create a new ScriptExecutor that is similar to the current one, but produces .dll and .pdb files when the -d flag is provided to scriptcs.
2. Create a readme that explains how to work with mdbg and scriptcs.

Let me know if you would like to have a discussion about any of this.

@glennblock
Scriptcs member
@glennblock
Scriptcs member
@dschenkelman
Scriptcs member

The idea of having a new executor is that the normal (current one) won't generate the .dll and the .pdb. The -debug activated one will. They will probably end up inheriting from a base class and overriding just one or two methods.

I like #break better because it is less verbose. We should automatically add System.Diagnostics if -debug is provided.

@dschenkelman
Scriptcs member

I've created #76 and will start to work on it.

@glennblock
Scriptcs member
@dschenkelman
Scriptcs member
@dschenkelman
Scriptcs member

This one should probaly be closed. At least for the time being there probably won't be any major updates in debugging per se.

@jrusbatch
Scriptcs member

Closing in favor of #76.

@jrusbatch jrusbatch closed this Mar 11, 2013
@glennblock
Scriptcs member

@dschenkelman I got a pointer from the Roslyn team that might be worth investigating aroun the #line directive: http://blogs.msdn.com/b/abhinaba/archive/2005/10/10/479016.aspx

This "might" allow doing the source mapping if the CTP supports it.

@glennblock glennblock reopened this Mar 12, 2013
@dschenkelman
Scriptcs member

@glennblock Wow! That absolutely did the trick. We definitely need to address #71 before adding support for this, but it seems to be definitely working!
debuggingInVs

@dschenkelman
Scriptcs member

Been going over the opened pull requests. We would need to:
1. Merge #92 and #102.
2. Implement #71 (I have it assigned, but is waiting for #102).

After that I will work automatically generating #line directives correctly #104.

@glennblock
Scriptcs member

@dschenkelman awesome!!!!!

@BrendanThompson

@dschenkelman this is amazingly awesome!!

@jrusbatch
Scriptcs member

@glennblock @dschenkelman Agreed, 100%. Best screen shot I've seen all day.

@dschenkelman
Scriptcs member

Thanks guys, I'm really having a good time as I can do something fun while also adding value.

@glennblock
Scriptcs member

@dschenkelman and we're having a good time with you!

@dschenkelman
Scriptcs member

@glennblock @filipw @jrusbatch
Should be closed. #86 is open to track the pending work.

@jrusbatch jrusbatch closed this Mar 13, 2013
This was referenced Dec 3, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.