Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DebugSymbols is ignored if DebugType is set. #2169

Open
distantcam opened this issue Jun 5, 2017 · 7 comments
Open

DebugSymbols is ignored if DebugType is set. #2169

distantcam opened this issue Jun 5, 2017 · 7 comments
Labels
backlog Documentation Issues about docs, including errors and areas we should extend (this repo and learn.microsoft.com) Priority:2 Work that is important, but not critical for the release triaged

Comments

@distantcam
Copy link

distantcam commented Jun 5, 2017

The Fody project recently had an issue where debug symbols were not being processed properly in release mode.

Fody/Fody#360

It turns out that the value of DebugSymbols is completely ignored if DebugType is set.

https://github.com/Microsoft/msbuild/blob/master/src/Tasks/Microsoft.Common.CurrentVersion.targets#L145-L151

<_DebugSymbolsProduced>false</_DebugSymbolsProduced>
<_DebugSymbolsProduced Condition="'$(DebugSymbols)'=='true'">true</_DebugSymbolsProduced>
<_DebugSymbolsProduced Condition="'$(DebugType)'=='none'">false</_DebugSymbolsProduced>
<_DebugSymbolsProduced Condition="'$(DebugType)'=='pdbonly'">true</_DebugSymbolsProduced>
<_DebugSymbolsProduced Condition="'$(DebugType)'=='full'">true</_DebugSymbolsProduced>
<_DebugSymbolsProduced Condition="'$(DebugType)'=='portable'">true</_DebugSymbolsProduced>
<_DebugSymbolsProduced Condition="'$(DebugType)'=='embedded'">false</_DebugSymbolsProduced>

But according to the documentation DebugSymbol should control the output of the symbol file.

https://msdn.microsoft.com/en-us/library/bb629394.aspx

DebugSymbols A boolean value that indicates whether symbols are generated by the build. Setting /p:DebugSymbols=false on the command line disables generation of program database (.pdb) symbol files.

DebugType Defines the level of debug information that you want generated. Valid values are "full," "pdbonly," and "none."

Is this a bug?

@SimonCropp
Copy link
Contributor

related #2170

@mikebeaton
Copy link

If you modify "VS/project properties/Build/Advanced/Debugging information" then it creates (if necessary) and sets the value of <DebugType> in the .csproj file, but does not set or add a <DebugSymbols> property.

So, if I've understood correctly, it's probably worth noting here that project files generated by Visual Studio rely on the behaviour under discussion.

@daiplusplus
Copy link

daiplusplus commented Feb 17, 2024

As of Feburary 2024, long after the introduction of /debug:embedded and release-debug-plus optimization level, the actual behaviour of the <DebugSymbols> and <DebugType> MSBuild properties does not match the current documentation at all - (while @distantcam linked to the VS2015 docs, the current docs for VS2022 has the same incorrect definitions):

<DebugSymbols>
A boolean value that indicates whether symbols are generated by the build.

<DebugType>
Defines the level of debug information that you want generated. Valid values are "full," "pdbonly," "portable", "embedded", and "none."

<DefineDebug>
A boolean value that indicates whether you want the DEBUG constant defined.

In actuality, in the .NET SDK version 7.0 (and also likely 5.0, 6.0, and 8.0, I haven't thoroughly checked those) the actual behavior is:

  • <DebugSymbols>

    • The value of <DebugSymbols> goes through to <Csc EmitDebugInformation="$(DebugSymbols)" (in Microsoft.CSharp.CurrentVersion.targets)
      • EmitDebugInformation="false" translates to csc.exe /debug- which:
        • Disables PDB output entirely if /debug- appears after /debug:DebugType. This is an undocumented surprise.
        • Does not disable PDB output entirely if /debug- appears before /debug:DebugType.
        • The MSBuild <Csc> task is hardcoded to output /debug+ or /debug- immediately before the /debug:DebugType option, btw.
      • EmitDebugInformation="true" translates to csc.exe /debug+
        • Enables PDB output and enables debugPlus optimizations.
  • <DebugType>

    • The value of <DebugSymbols> goes through to <Csc DebugType="$(DebugType)" (in Microsoft.CSharp.CurrentVersion.targets)
    • Special-case: if DebugType="none" then this also clears the <Csc EmitDebugInformation="" attribute, which affects program optimization.
  • <DefineDebug>

    • This only seems to affect VB.NET compilation, and not C# at all, as it only appears in these files:
      • Microsoft.VisualBasic.CurrentVersion.targets
      • Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.VisualBasic.props
      • Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.VisualBasic.targets
    • And, to my surprise, it seems no part of MSBuild or csc will define DEBUG at all, so this now always requires an explicit user MSBuild <DefineConstants>$(DefineConstants);DEBUG property, wow.

So, if I were writing the documentation, I'd change the text to something like:

<DebugSymbols>

  • When true then:
    • PDB debugging information will be included in the output (either as a separate .pdb file or embedded within the output .exe/.dll).
    • Enables debugPlus optimization mode in both Release and Debug configurations . This debugPlus Disables certain IL-level optimizations, even in Release builds - or (aka debugPlus).
  • When false then:
    • Disables PDB output (including embedded PDB data) - but only when <DebugType> is not specified.
    • Prevents debugPlus IL output (thus forcing either Release or Debug depending on other options).
  • When undefined then:
    • PDB output is then controlled by <DebugType>.
    • Prevents debugPlus IL output (thus forcing either Release or Debug depending on other options).

<DebugType>

  • When none then:
    • Any <DebugSymbols> property value becomes undefined (thus debugPlus-level optimization will never be applied).
    • Disables PDB output (whether separate-file or embedded)
  • When full, pdbonly, or portable then:
    • Enables PDB output as a separate file. The exact file-format of the .pdb file depends on your OS platform when using full or pdbonly.
    • As of the .NET 7 SDK. provided you are not targeting any legacy platforms then the full and pdbonly values are both redundant and superfluous, instead use only portable or embedded.
  • When embedded then:
    • Enables PDB output as extra data embedded directly in the output .dll/.exe file. The same exact format is used regardless of OS platform.
  • When undefined then:
    • PDB output will be disabled unless <DebugSymbols>true is explicitly set.
    • PDB exact file format will depend on your OS platform.

<DefineDebug>

  • This property is not respected by the C# build system. Instead set an explicit property like so <DefineConstants>$(DefineConstants);DEBUG;</DefineConstants>

So yes - quite the difference...

Furthermore, the documentation on Optimization is incorrect, it says DebugPlusMode is ignored when OptimizationLevel.Release is set, but in reality it's the opposite: debugPlus == true with Release mode gives us ILEmitStyle.DebugFriendlyRelease, while debugPlus has no effect when CompilationOptions.Optimizationlevel == OptimizationLevel.Debug (c.f. Microsoft.CodeAnalysis.CSharp.MethodCompiler.GenerateMethodBody).

So currently, my default Directory.Build.props looks like this:

	<PropertyGroup Condition="$(Configuration) = 'Debug'">
		<!-- This combination of properties causes CSC to compile without IL optimizations, and to emit a very debuggable embedded PDB.  -->
		<DebugType>embedded</DebugType>
		<DebugSymbols>true</DebugSymbols>
		<DefineConstants>$(DefineConstants);DEBUG;</DefineConstants>
	</PropertyGroup>
	<PropertyGroup Condition="$(Configuration) = 'Release'">
		<!-- This combination of properties causes CSC to compile with "release-debug-plus" optimizations. The evidence suggests this has an absolutely negligible performance impact, while significantly improving the debugging experience. -->
		<DebugType>embedded</DebugType>
		<DebugSymbols>true</DebugSymbols>
		<Optimize>true</Optimize>
		<DefineConstants>$(DefineConstants);RELEASE;</DefineConstants>
	</PropertyGroup>

If it helps, I've put together this table:

<DebugType> <DebugSymbols> <Optimize> Effective csc.exe options Effective CompilationOptions Debugability
embedded false false /debug- /debug:embedded /optimize- DebugPlusMode=false, Optimization.Debug, emitPdb=true, emitPdbFile=false High (Debug mode; DebugPlusMode = false)
embedded false true /debug- /debug:embedded /optimize+ DebugPlusMode=false, Optimization.Release, emitPdb=true, emitPdbFile=false Low (Release mode, DebugPlusMode = false)
embedded true false /debug+ /debug:embedded /optimize- DebugPlusMode=true, Optimization.Debug, emitPdb=true, emitPdbFile=false High (Debug mode; DebugPlusMode = true but is ignored)
embedded true true /debug+ /debug:embedded /optimize+ DebugPlusMode=true, Optimization.Release, emitPdb=true, emitPdbFile=false Medium (Release mode, DebugPlusMode = true)

daiplusplus added a commit to daiplusplus/murmurhash that referenced this issue Feb 17, 2024
@AR-May AR-May added needs-attention Needs a member of the team to respond. backlog Priority:2 Work that is important, but not critical for the release Documentation Issues about docs, including errors and areas we should extend (this repo and learn.microsoft.com) and removed needs-attention Needs a member of the team to respond. labels Feb 21, 2024
Morilli added a commit to TASEmulators/BizHawk that referenced this issue Apr 3, 2024
- use embedded debug information instead of portable pdbs
- always emit debug symbols. The `DebugSymbols` property is kind of bugged (see dotnet/msbuild#2169), but it does have advantages when set
@AR-May AR-May added the triaged label Apr 4, 2024
@Rabadash8820
Copy link

@daiplusplus Many thanks for the deep dive!

These properties have been confusing me lately as well. Out of curiosity, in your "default Directory.Build.props", why do you still use embedded debug symbols in the Release configuration? I suppose it might not be a big deal for a server-side app, but for desktop/mobile apps, doesn't that just make it easier for end users to reverse engineer your assemblies? I always thought portable was preferrable in Release.

@daiplusplus
Copy link

daiplusplus commented May 16, 2024

@Rabadash8820 but for desktop/mobile apps, doesn't that just make it easier for end users to reverse engineer your assemblies?

  • PDB files only add the names of method-locals, whereas your .exe/.dll files already contain namespaces, type-names, parameters and some locals (as a by-product of how the compiler generates the type representing closures for async methods, etc). So ultimately it doesn't matter because tools like ILSpy have gotten so good now I genuinely can't tell whether or not PDB symbols were available when I've disassembled a .NET program. If a PDB file makes the difference between your program being 0wned or not, then you've probably got an unmaintainable 50,000 line function there.
  • Whereas having symbols available when exception stack-traces are generated means you get better stack-traces - otherwise things like the line-numbers tend to be wrong. If .pdb symbols weren't embedded then I'd have to find somewhere to stick them all (i.e. a Symbol Server) - and I'd have to retain them indefinitely because many of my projects release nightlies to downstream, and in 7 years time some huge enterprise customer is going to come knocking with a bug report that I won't be able to help them with.
  • And speaking for myself, I'm not concerned if anyone uses ILSpy on my binaries, if anything, I'd be flattered that my work was worth anyone's curious attention like that.
    • I know .NET obfuscators exist - they also make troubleshooting memory-dumps a huge pain, even when their obfuscated-stack-trace-translator tools actually work.
    • ...and if you're writing anything where the suits are concerned about binaries being disassembled then you should not be writing in C#/.NET in the first place.

I always thought portable was preferrable in Release.

Whatever is "preferable" for any given situation is subjective; in my case, it is demonstrably not preferable (c.f.: arguments above), but I appreciate that in yours it might be.

@Rabadash8820
Copy link

@daiplusplus Thanks for the thoughtful response. I guess the reverse engineering concern is a bit overblown; I too would be flattered if somebody actually cared about my software enough to try that lol, and security through obscurity is not real security anyway, plus most of what I write is open-source anyway. 🤷‍♂️

I guess a bigger concern about embedded PDBs in Release is file size. I know assemblies and PDBs are typically not huge (on the order of 10s of MB maybe), but as the number of types and assemblies grows, the savings of not shipping a PDB becomes non-trivial. In particular, do you know if assemblies contain private/protected member/type names as well, or only public ones? If it's only public, then I could see PDBs being quite large by providing all those extra symbols. These files can be hosted elsewhere as you suggested to keep the shipped app smaller. In fact, several SaaS systems for mobile already provide hosting for symbolicating exception call stacks from crashes (Firebase Crashlytics and Unity Cloud Diagnostics do anyway). But for server-side code I guess you're right, there's no big reason against embedded symbols.

@slang25
Copy link

slang25 commented May 17, 2024

I don't think that's the right way of thinking about it, regardless of public/private types and methods, the assembly contains all of the definitions. The PDB contains local variable names, info about scopes, and metadata about methods, importantly the IL offsets to source code mapping.

So it's generally proportional to how big the assembly is, and that's around 15%.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backlog Documentation Issues about docs, including errors and areas we should extend (this repo and learn.microsoft.com) Priority:2 Work that is important, but not critical for the release triaged
Projects
None yet
Development

No branches or pull requests

7 participants