📜 ⬆️ ⬇️

A couple of stories about the differences Release from Debug

All developers know that the release version may differ from the debug version. In this article I will tell a couple of cases from the life when such differences led to the erroneous execution of the program. The examples are not very complex, but they may well protect against a rake attack.

Story 1



Actually, it all started with the fact that the bug came that the application crashes with some operations. This happens often. The bug did not want to be played in the debug version. It sometimes happens. Since the application part of the library was written in C ++, the first thought was something like "somewhere forgot to initialize the variable or something like that." But in fact, the essence of the bug was in the managed code, although it was not without control.
')
And the code was approximately as follows:

class Wrapper : IDisposable
{
public IntPtr Obj { get ; private set ;};

public Wrapper()
{
this .Obj = CreateUnmObject();
}

~Wrapper()
{
this .Dispose( false );
}

protected virtual void Dispose( bool disposing)
{
if (disposing)
{
}

this .ReleaseUnmObject( this .Obj);
this .Obj = IntPtr .Zero;
}

public void Dispose()
{
this .Dispose( true );
GC.SuppressFinalize( this );
}
}


* This source code was highlighted with Source Code Highlighter .

In principle, the canonical implementation of the IDisposable template is practically (“practically” - because there is no variable disposed, instead of it the pointer is reset), a completely standard wrapper class of the unmanaged resource.

The class was used approximately as follows:
{
Wrapper wr = new Wrapper();
calc.DoCalculations(wr.Obj);
}


* This source code was highlighted with Source Code Highlighter .

Naturally, the attentive reader will immediately notice that the wr object must be called Dispose, that is, it must be wrapped with the using construct. But at first glance, this should not affect the cause of the fall, since the difference will be whether the resource is determined or not.

But in fact, there is a difference in the release build. The fact is that the wr object becomes available to the garbage collector immediately after the start of the implementation of the DoCalculations method, because there is no longer any “live” object who would refer to it. This means that wr may well (and it did) be destroyed during the execution of DoCalculations and the pointer passed to this method becomes invalid.

If you wrap the call to DoCalculations in using (Wrapper wr = new Wrapper ()) {...}, then this solves the problem, because a call to Dispose in a finally block will not allow the greedy garbage collector to “eat” the object ahead of time. If for some reason we cannot or do not want to call Dispose (for example, WPF, this template does not complain at all), then we will have to insert GC.KeepAlive (wr) after calling DoCalculations.

The real code, of course, was more difficult and seeing the error in it was not as easy as in the example.

Why did the error manifest itself only in the Release-version, and then it was launched not from under the studio (if you connect the debugger during the execution, the error will be repeated)? Because otherwise, anchors are added for all local reference variables to live to the end of the current method, this was done explicitly for the convenience of debugging.

Story 2



Once upon a time there was a project where a manager was used to access the resources and, using a string key, retrieved resources from a given assembly. In order to facilitate the writing of code, the following method was written:
public string GetResource( string key)
{
Assembly assembly = Assembly .GetCallingAssembly();
return this .GetResource(assembly, key);
}


* This source code was highlighted with Source Code Highlighter .


After migrating to .Net 4, some resources suddenly ceased to be. And the thing is again in the release version optimization. The fact is that in version 4 of a dneet, the compiler can embed calls into the code of methods of other assemblies.

To "feel the difference" the following example is offered:

dll1:
public class Class1
{
public void Method1()
{
Console .WriteLine( new StackTrace());
}
}

dll2:
public class Class2
{
public void Method21()
{
this .Method22();
}

public void Method22()
{
( new Class1()).Method1();
}
}

dll3:
class Program
{
static void Main( string [] args)
{
( new Class3()).Method3();
}
}
class Class3
{
public void Method3()
{
( new Class2()).Method21();
}
}


* This source code was highlighted with Source Code Highlighter .


If you compile in the debug configuration (or if you run the process from under the studio), you will get an honest call stack:
in ClassLibrary1.Class1.Method1 ()
in ClassLibrary2.Class2.Method22 ()
in ClassLibrary2.Class2.Method21 ()
in ConsoleApplication1.class3.method3 ()
in ConsoleApplication1.Program.Main (String [] args)

If you build under .Net versions up to 3.5 inclusive in the release:
in ClassLibrary1.Class1.Method1 ()
in ClassLibrary2.Class2.Method21 ()
in ConsoleApplication1.Program.Main (String [] args)

And under .Net 4 in the release configuration, we get:
in ConsoleApplication1.Program.Main (String [] args)

The moral here is simple - you should not tie the logic to the call stack, as well as to be surprised at the unusual stack of exceptions in the release version log. In particular, if you try to find the cause of an exception solely by its call stack, you should consider that if the stack ends on Method1, then in code it (exception) could be generated in one of the small methods that are called in Method1 body.

Also just in case it is worth remembering that you can prevent the compiler from embedding a method by marking it with the attribute [MethodImpl (MethodImplOptions.NoInlining)], a kind of __declspec (noinline) in VC ++.

Instead of conclusion



The world of only the release of bugs is truly limitless, and I did not set the goal of making a full review of it. I just wanted to share my own experience, or rather a more interesting part of it. Well, it remains only to wish readers less to encounter similar errors in the work.

Source: https://habr.com/ru/post/127543/


All Articles