Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Investigate Debugging story #68

Closed
glennblock opened this Issue · 29 comments

5 participants

@glennblock
Owner

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
Collaborator

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
Owner

@dschenkelman no rush.

@dschenkelman
Collaborator

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
Owner
@glennblock
Owner
@glennblock
Owner
@dschenkelman
Collaborator

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
Collaborator

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
Owner
@dschenkelman
Collaborator

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
Owner

Nice!

@dschenkelman
Collaborator

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
Owner
@glennblock
Owner
@dschenkelman
Collaborator

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
Collaborator

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

@glennblock
Owner
@dschenkelman
Collaborator
@dschenkelman
Collaborator

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
Owner

Closing in favor of #76.

@jrusbatch jrusbatch closed this
@glennblock
Owner

@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
@dschenkelman
Collaborator

@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
Collaborator

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
Owner

@dschenkelman awesome!!!!!

@BrendanThompson

@dschenkelman this is amazingly awesome!!

@jrusbatch
Owner

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

@dschenkelman
Collaborator

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

@glennblock
Owner

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

@dschenkelman
Collaborator

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

@jrusbatch jrusbatch closed this
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.