Opened 5 years ago

Last modified 5 years ago

#13245 new Bugs

Coroutines2: Crashes Visual Studio when attached with a debugger (on Windows x86)

Reported by: Michael Eiler <michael.eiler@…> Owned by: olli
Milestone: To Be Determined Component: context
Version: Boost 1.65.0 Severity: Showstopper
Keywords: coroutine2, context Cc:

Description

Hi,

In my application I'm using coroutine2 to generate some objects which I have to decode from a stream. These objects are generated using coroutines. My problem is that as soon as I reach the end of the stream and would theoretically throw std::ios_base::failure my application crashes under certain conditions.

The function providing this feature is implemented in C++, exported as a C function and called from C#. This all happens on a 32bit process on Windows 10 x64. Unfortunately it only reliably crashes when I start my test from C# in debugging mode WITHOUT the native debugger attached. As soon as I attach the native debugger everything works like expected.

Here is a small test application to reproduce this issue:

Api.h

#pragma once
extern "C" __declspec(dllexport) int __cdecl test();

Api.cpp

#include <iostream>
#include <vector>
#include <sstream>
#include "Api.h"

#define BOOST_COROUTINES2_SOURCE
#include <boost/coroutine2/coroutine.hpp>

int test()
{
	using coro_t = boost::coroutines2::coroutine<bool>;

	coro_t::pull_type source([](coro_t::push_type& yield) {
		std::vector<char> buffer(200300, 0);
		std::stringstream stream;
		stream.write(buffer.data(), buffer.size());

		stream.exceptions(std::ios_base::eofbit | std::ios_base::badbit | std::ios_base::failbit);

		try {
			std::vector<char> dest(100100, 0);
			while (stream.good() && !stream.eof()) {
				stream.read(&dest[0], dest.size());
				std::cerr << "CORO: read: " << stream.gcount() << std::endl;
			}
		}
		catch (const std::exception& ex) {
			std::cerr << "CORO: caught ex: " << ex.what() << std::endl;
		}
		catch (...) {
			std::cerr << "CORO: caught unknown exception." << std::endl;
		}
	});

	std::cout << "SUCCESS" << std::endl;
	return 0;

}

c#:

using System;
using System.Runtime.InteropServices;

namespace CoroutinesTest
{
    class Program
    {
        [DllImport("ConsoleApplication1.dll", EntryPoint = "test", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        internal static extern Int32 test();

        static void Main(string[] args)
        {
            test();
            Console.WriteLine("SUCCESS");
        }
    }
}

Some details:

  • We are using Visual Studio 2015 14 and dynamically link the c++ runtime.
  • The test library statically links Boost 1.63.0.
  • We also tried to reproduce this behaviour with calling the functionallity directly from c++ and from python. Both tests have not been successful so far.
  • If you start the c# code with CTRL F5 (meaning without the .net debugger) everything will also be fine. Only if you start it with F5 (meaning the .NET Debugger attached) the visual studio instance will crash. Also be sure not to enable the native debugger!
  • Note: If we don't use the exceptions in the stream, everything seams to be fine as well. Unfortunately the code decoding my objects makes use of them and therefore I cannot avoid this.

Would be amazing if you had some additional hints on what might go wrong here or a solution.

Thanks in advance! Best Regards, Michael

Change History (2)

comment:1 by Michael Eiler <michael.eiler@…>, 5 years ago

Component: coroutinecontext
Keywords: context added
Version: Boost 1.63.0Boost 1.65.0

Hi,

I did some further investigations and can provide some more details. The problem also occurs in the newest boost release (1.65.1). The behavior is still the same. It only crashes when the C# debugger (without native debugging) is attached but I can directly reproduce it with context, the coroutine2 wrapper around it is not necessary.

This is the updated content of the Api.cpp file:

#include <boost/coroutine2/coroutine.hpp>
#include <boost/context/all.hpp>

class Foo
{
public:
	void bar() {
		throw std::exception("Hello World!");
	}
};

int ApiInit()
{
	boost::context::continuation source = boost::context::callcc(
		[](boost::context::continuation && sink) {
			Foo foo;
			try {
				foo.bar();
			}
			catch (const std::exception& ex) {
				std::cerr << "caught ex: " << ex.what() << std::endl;
			}

			return std::move(sink);
		});

	if (source.operator bool())
		source = source.resume();

	return 0;
}

It's important that this is compiled without optimizations enabled (e.g. in debug mode) so that throwing the exception won't be optimized away. Because the actual issue is exactly the process of throwing an exception.

Best Regards,

Michael

comment:2 by Michael Eiler <michael.eiler@…>, 5 years ago

In a first quick test with boost 1.65.1 and windows fibers as implementation for boost-context I haven't been able to reproduce the issue anymore. My current suspision is that the .Net Debugger in some circumstances might use additional registers which are not backuped by the default fcontext implementation.

Note: See TracTickets for help on using tickets.