0

First of all ...
I tried posting this question earlier, but didn't explain it properly, and it was just confusing people. So I deleted the old question and tried a complete re-write, with a completely different title.

And ... I've searched stack overflow, and there are related questions, but none of them answer this question. I would be very grateful for any help you could provide. The supplied answers on other stackoverflow questions don't solve the problem of compiler and dotfuscator changing the line numbers. I'm looking for a way to implement the VB solution below in a C# program

My test system:
-Windows 10
-VS2022
-C#
-.Net Framework 4.8
-WinForms

What I'm Trying to Accomplish:
We are trying to build an error reporting system in C# that will give the exact line number that throws an exception. It needs to work with the compiled release .exe file. It is easy to do this in VB.net, but I can't find a way to accomplish this in C#.

Here is how to get an exact location in VB.net:

    10:     On Error Resume Next
    20:     Err.Raise(60000)
    ' Returns 20.
    30:     MsgBox(Erl())

The above code is from the Microsoft documentation here, showing usage of the ErrObject.Erl property, which was carried over from VB6. I used that system to build an extensive error reporting capability back when I was a VB6 programmer. It was an extremely useful tool that allowed any end-user in the world to report detailed error information back to the Mother Ship (the developers). This allowed us to rapidly home in on the problem and do the necessary re-factoring.

Of course it is better to eliminate errors to begin with, but when shareware is being downloaded by a million users all over the world, on many different versions of Windows, with varying locality settings, there are going to be errors that don't pop up in beta testing.

Unfortunately, I'm not able to find any way in C# to add number tags at the beginning of code lines, like the 10:, 20:, 30: above. Those are not Visual Studio line numbers ... they are typed in by the programmer to label each line in the code as described in the Microsoft documentation here. I've also not found any way to get the Microsoft ErrObject working in C#, like it does in VB.net.

Here is what I've tried in C#:
Here is my test code:

    private void button2_Click(object sender, EventArgs e)
    {
        try
        {
            int x = 10;      // This is line 38 in the editor window
            int y = 0;
            int z = 0;
            z = x / y;       // This is line 41,throwing the exception (divide by zero)
        }
        catch (System.Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }

The Problem:
The ex.ToString() always returns the first line of the try block (in this example, line 38), instead of returning the actual line that triggered the error (line 41). This is ALSO TRUE OF the other answers on stack overflow, involving StackTrace, etc.

My Question:
Is there a way in C# to get the exact code line that throws the exception, like we can do in VB.net? If so, how?

One Possible Solution
One person suggested it might be possible to increment an integer variable on every other line of code, then report that integer value with the exception. I appreciate that kind of creative thinking, but I'm hoping there will be other ideas as well. Thanks!!

Thanks!!
Any help you could provide would be sincerely appreciated.

.

MontanaMan
  • 177
  • 1
  • 1
  • 11
  • 2
    Does this answer your question? [How can I get the line number which threw exception?](https://stackoverflow.com/questions/3328990/how-can-i-get-the-line-number-which-threw-exception) – GSerg Mar 11 '22 at 22:50
  • Hi GSerg ... thanks, but that doesn't give the exact line number, as is explained in my question (See "The Problem"). But I do appreciate your willingness to help!! Best wishes to all who read this. – MontanaMan Mar 11 '22 at 23:02
  • What does it give if not the line number? – GSerg Mar 11 '22 at 23:05
  • Hi Greg: Please read the section labeled "The Problem:" in bold. Thanks! – MontanaMan Mar 11 '22 at 23:10
  • 1
    [Shows 12 for me](https://dotnetfiddle.net/5MOCD2), which is the exact line. – GSerg Mar 11 '22 at 23:17
  • Shows the correct line # for me. – quaabaam Mar 11 '22 at 23:17
  • I think it's likely the compiler optimizations enabled by default in release mode are changing the apparent line number of the error. – ScottyD0nt Mar 11 '22 at 23:20
  • Are you sure you are viewing the correct build on the exe? Maybe you are running a build that doesn't match the code you are looking at and this is leading you to believe the line is wrong? – quaabaam Mar 11 '22 at 23:20
  • Wow ... thanks to everyone for responding. Just to be clear ... your system is showing the line that says "z = x / y;" ??? Mine *always* says whatever line number that is first in the try block. I've saved, built, and then run immediately. – MontanaMan Mar 11 '22 at 23:27
  • @ScottyD0nt Yes ... you are right. I ran it in Debug Mode, and it gave the correct line number. So ... I think ex.ToString() might not be the best solution. Is there any way to implement the VB.Net solution in my question? Anybody know how to use that in C#? – MontanaMan Mar 11 '22 at 23:35
  • Yeah ... the problem is that debug will give the correct line number, but release will always give the wrong number, due to the compiler issues ... *ESPECIALLY* after dotfuscator. Also ... release will still require the PDB files, which we'd prefer to not distribute to end users. – MontanaMan Mar 11 '22 at 23:47
  • https://stackoverflow.com/questions/12556767/how-do-i-get-the-current-line-number – quaabaam Mar 12 '22 at 00:29
  • @quaabaam Thanks, but that doesn't solve the problem of the compiler changing the line numbers in the release version ... especially after dotfuscator – MontanaMan Mar 12 '22 at 00:32
  • 2
    So you want the correct line number from compiled code that was obfuscated. Seriously? – Loathing Mar 12 '22 at 05:11
  • @Loathing Thank you for your comment. Yes ... that's what I wanted, and I think we have it solved. Please see my answer. Thanks! – MontanaMan Mar 12 '22 at 22:18

3 Answers3

2

Ok ... we have the solution. You can run the compiler optimized, then run the Dotfuscator, and still get the exact error line number reported by the System.Exception Class. And ... there are no PDB files to help hackers crack your software.

Just set Debugging information to "Embedded."

Instructions for .Net Framework 4.8 instructions (Current as of VS2022):
(Step-by-Step to help new people)

  • Right-Click on your project and select "Properties"
  • In the left panel of the Properties window, select "Build"
  • Make sure that "Optimize Code" is selected (or not ... it's your choice)
  • Click the "Advanced" button, at the bottom-right of the screen
  • In the Advanced window, about 2/3 down, you'll see "Debugging information:" with a drop-down box.
  • Select "Embedded" and click "OK". The Advanced window disappears.

Instructions for .Net 6.0 (Current as of VS2022):

  • Right-Click on your project and select "Properties"
  • In the left panel of the Properties window, select "Build > General"
  • Under "Optimize Code", select "Release" (or not ... it's your choice)
  • Directly under "Optimize Code, you'll see "Debug Symbols" with a drop-down box.
  • Select "Embedded in DLL/EXE".
  • Close the Project Properties window.

Now ... rebuild (REbuild!) the release version with compiler optimized and debug set to embedded. Then run the dotfuscator, and your dotfuscated Release .exe will still get the accurate line number from the following code:

    catch (System.Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }

Obviously, this example is bare-bones, but you can build a robust error-reporting system that includes this and other features built into the System.Exception Class (Microsoft documentation here). We intend to build a website that will receive the information from the desktop app, and store it into a DB as part of a basic service ticket system.

Performance Tests:

I set up a small app that runs a loop of simple math operations 200,000,000 repetitions, timing the results (average of 3 runs). Here are the results:

Compiler Optimized - Debugging information set to "None" - Dotfuscated:
Time: 4116.3 ms (no line number reported)

Compiler Optimized - Debugging information set to "Embedded" - Dotfuscated
Time: 4115.9 ms (exact line number reported!)

Hope this helps anybody who is interested in creating an error-reporting system for their software.

Best of luck to all!!

MontanaMan
  • 177
  • 1
  • 1
  • 11
  • From what I have read, `Embedded` is similar to `Portable` except that the `portable` debug file is embedded within the `exe` file. Does `Portable` also maintain the correct line numbers? So the issue is that `pdb-only` doesn't preserve the line numbers? Seems like an odd inconsistency. – Loathing Mar 13 '22 at 06:22
  • Were there any other settings that might affect this? I've tried your solution on a simple C# .NET 6.0 console app and still get different line numbers for optimized vs non-optimized code, even with the embedded symbols. – sclarke81 Feb 24 '23 at 12:36
0

Try this:

try
{
    int x = 10;      // This is line 38 in the editor window
    int y = 0;
    int z = 0;
    z = x / y;       // This is line 41,throwing the exception (divide by zero)
}
catch (Exception ex)
{
   int CodeLine = new System.Diagnostics.StackTrace(ex, true).GetFrame(0).GetFileLineNumber();
   MessageBox.Show(CodeLine.ToString());
}
Jonathan Barraone
  • 489
  • 1
  • 3
  • 20
  • 1
    Thanks, but that is reporting the wrong number, due to the compiler optimizations, as ScottyD0nt explained. Is there a way to implement that VB.net solution in C#? – MontanaMan Mar 11 '22 at 23:39
0

Getting the original line number from obfuscated code seems like a product feature that should be handled by the obfuscator. Alternatively, you could pre-process your the original .cs files, and the pre-processed files get passed to the obfuscator. For example, your input code is:

34 private void button2_Click(object sender, EventArgs e)
35    {
36        try
37        {
38            int x = 10;      // This is line 38 in the editor window
39            int y = 0;
40            int z = 0;
41            z = x / y;       // This is line 41,throwing the exception (divide by zero)
42        }
43        catch (System.Exception ex)
44        {
45            MessageBox.Show(ex.ToString());
46        }
47    }

Your pre-process takes the above code and inserts line numbers. Then this generated code is passed to the obfuscator.

private void button2_Click(object sender, EventArgs e)
    {
        int lineNumber = 36;
        try
        {
            lineNumber = 38;
            int x = 10;      // This is line 38 in the editor window
            lineNumber = 39;
            int y = 0;
            lineNumber = 40;
            int z = 0;
            lineNumber = 41;
            z = x / y;       // This is line 41,throwing the exception (divide by zero)
            lineNumber = 42;
        }
        catch (System.Exception ex)
        {
            // decide how to handle Exceptions. Possibly wrap the exception in a new custom exception
            // or write the line number and file to a log file
            throw new LineNumberWrapperException(lineNumber, ex);
        }
    }
Loathing
  • 5,109
  • 3
  • 24
  • 35
  • Thanks for this answer ... your solution would definitely work, but might involve more processing. I think setting debug to "Embedded" allows us to get the same results. Thanks! – MontanaMan Mar 12 '22 at 22:20