Skip to content

Standardize EventSource behavior in ETW for null values - #77172

Closed
JJamesWWang wants to merge 9 commits into
dotnet:mainfrom
JJamesWWang:fix/event-source-null-arg-12662
Closed

Standardize EventSource behavior in ETW for null values#77172
JJamesWWang wants to merge 9 commits into
dotnet:mainfrom
JJamesWWang:fix/event-source-null-arg-12662

Conversation

@JJamesWWang

@JJamesWWangJJamesWWang commented Oct 18, 2022

Copy link
Copy Markdown
Contributor

fixes#12662

@ghostghost added area-System.Diagnostics.Tracing community-contribution Indicates that the PR has been added by a community member labels Oct 18, 2022
@ghost

Copy link
Copy Markdown

Tagging subscribers to this area: @tarekgh, @tommcdon, @pjanotti
See info in area-owners.md if you want to be subscribed.

Issue Details

null

Author:JJamesWWang
Assignees:-
Labels:

area-System.Diagnostics.Tracing

Milestone:-

@JJamesWWang

JJamesWWang commented Oct 18, 2022

Copy link
Copy Markdown
ContributorAuthor

This is the test failure based on the test case added.

BasicEventSourceTests.TestsWriteEventToListener.Test_WriteEvent_ArgsBasicTypes[FAIL]Assert.Null() Failure
Expected:(null)
Actual:
Stack Trace:
C:\Users\JJame\Documents\OSSE\runtime\src\libraries\System.Diagnostics.Tracing\tests\BasicEventSourceTest\TestsWriteEventToListener.cs(208,0): at BasicEventSourceTests.TestsWriteEventToListener.Test_Wr iteEvent_ArgsBasicTypes()
at System.RuntimeMethodHandle.InvokeMethod(Objecttarget,Void**arguments,Signaturesig,BooleanisConstructor)C:\Users\JJame\Documents\OSSE\runtime\src\libraries\System.Private.CoreLib\src\System\Reflection\MethodInvoker.cs(64,0):atSystem.Reflection.MethodInvoker.Invoke(Objectobj,IntPtr*args,BindingFlagsinvokeAttr)

@JJamesWWang

Copy link
Copy Markdown
ContributorAuthor

@AaronRobinsonMSFT re: #12662
This seems to be a valid failure to me; do you have any insight into what the error is?

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

@AaronRobinsonMSFT re: #12662 This seems to be a valid failure to me; do you have any insight into what the error is?

This is saying that the value at index 0 isn't null. Which does seem odd for the current scenario, but it might be intentional converting a null to the string "(null)". Can you share what the value actually is?

@JJamesWWang

JJamesWWang commented Oct 18, 2022

Copy link
Copy Markdown
ContributorAuthor

Can you share what the value actually is?

I added this in:

Console.WriteLine("------------------------------------------");Console.WriteLine((string)LoudListener.t_lastEvent.Payload[0]);Console.WriteLine("------------------------------------------");

and the output is:

------------------------------------------------------------------------------------

which leads me to believe it is the empty string.

Adding the following assert statement before the Assert.Null seems to prove it:

Assert.Equals("",(string)LoudListener.t_lastEvent.Payload[0]);

as it still fails on the Assert.Null

It seems that this is what is being called, and it looks consistent for the rest of the WriteEvent calls.

Is it intentional that null values become the empty string then?

@JJamesWWangJJamesWWang changed the title EventListener: Add test for logging a null stringEventListener: Add test for logging a null string in an EventSourceOct 18, 2022
@AaronRobinsonMSFT

Copy link
Copy Markdown
Member

I added this in:

Console.WriteLine("------------------------------------------");
Console.WriteLine((string)LoudListener.t_lastEvent.Payload[0]);
Console.WriteLine("------------------------------------------");

A good rule of thumb is to put quotes around the string. Like so:

Console.WriteLine($"'{(string)LoudListener.t_lastEvent.Payload[0]}'");

Is it intentional that null values become the empty string then?

@noahfalk or @davmason Any thoughts here?

@davmason

Copy link
Copy Markdown
Contributor

@noahfalk or @davmason Any thoughts here?

It looks like we special case null strings and make them empty strings here:

protectedunsafevoidWriteEvent(inteventId,string?arg1,string?arg2)
{
if(IsEnabled())
{
arg1??="";
arg2??="";
fixed (char*string1Bytes=arg1)
fixed (char*string2Bytes=arg2)
{
EventSource.EventData*descrs=stackallocEventSource.EventData[2];
descrs[0].DataPointer=(IntPtr)string1Bytes;
descrs[0].Size=((arg1.Length+1)*2);
descrs[0].Reserved=0;
descrs[1].DataPointer=(IntPtr)string2Bytes;
descrs[1].Size=((arg2.Length+1)*2);
descrs[1].Reserved=0;
WriteEventCore(eventId,2,descrs);
}
}
}

I'm taking a deeper look now to see if this means #12662 was fixed but never closed or if it is talking about a different scenario

@davmason

Copy link
Copy Markdown
Contributor

Ok, so this issue stills repros, but it's way more subtle than any of the previous discussion suggests.

The error happens specifically when an ETW or EventPipe session is enabled, and the WriteEvent(int eventId, params object?[] args) overload is used.

We have some specific overloads in EventSource for performance, but we fall back to this WriteEvent overload. We will translate the null string to an empty string in the overloads that explicitly take strings, and if using an EventListener we don't do any further processing and happily send a null event param.

If we are writing to an ETW/EventPipe session, however, we call in to EventProvider.WriteEvent and the code here sees that an argument is null and translates it to an error:

else
{
s_returnCode=WriteEventErrorCode.NullInput;
returnfalse;
}

To fix this the right way we should audit all the overloads of WriteEvent, see where we do null->default values for any type (I think it's not just string) and then make WriteEventVarargs match that behavior.

@davmason

davmason commented Oct 18, 2022

Copy link
Copy Markdown
Contributor

Repro for the failing case, you can make a console app and add the Microsoft.Diagnostics.NETCore.Client nuget package

usingSystem.Diagnostics.Tracing;usingSystem.Diagnostics;usingSystem;usingSystem.Threading.Tasks;usingSystem.Collections.Generic;usingMicrosoft.Diagnostics.NETCore.Client;usingMicrosoft.Diagnostics.Tracing;[EventSource(Name="Test.MyEventSource")]classEventSourceTest:EventSource{[Event(1)]publicvoidTestEvent(stringstr,inti,floatf,longl){WriteEvent(1,str,i,f,l,nullStr);}}classProgram{staticvoidMain(string[]args){List<EventPipeProvider>providers=newList<EventPipeProvider>{newEventPipeProvider("Test.MyEventSource",EventLevel.Verbose)};intprocessId=Process.GetCurrentProcess().Id;DiagnosticsClientclient=newDiagnosticsClient(processId);using(EventPipeSessionsession=client.StartEventPipeSession(providers,/* requestRunDown */false)){using(varsource=newEventSourceTest()){source.TestEvent(null,1,1,1);;varevents=newEventPipeEventSource(session.EventStream);TaskprocessTask=Task.Run(()=>{events.Dynamic.All+=(TraceEventtraceEvent)=>{Console.WriteLine($"Got event {traceEvent.EventName} with #args {traceEvent.PayloadNames.Length}");foreach(stringnameintraceEvent.PayloadNames){Console.WriteLine($" {name}: \"{traceEvent.PayloadByName(name)}\"");}};events.Process();});session.Stop();processTask.Wait();}}}}

@JJamesWWang

JJamesWWang commented Nov 4, 2022

Copy link
Copy Markdown
ContributorAuthor

How do I simulate the EventPipe session in a test case? I believe the following set of assertions will fail if executed while an ETW or EventPipe session is enabled (as per the failing case repro), but I'm not quite sure how to create a test under the same circumstances.

log.EventWithFallbackArgs(null,10,11,12);Assert.Equal(56,LoudListener.t_lastEvent.EventId);Assert.Equal(4,LoudListener.t_lastEvent.Payload.Count);Assert.Equal("",(string)LoudListener.t_lastEvent.Payload[0]);Assert.Equal(10,(int)LoudListener.t_lastEvent.Payload[1]);Assert.Equal(11,(float)LoudListener.t_lastEvent.Payload[2]);Assert.Equal(12,(long)LoudListener.t_lastEvent.Payload[3]);
[Event(56)]publicunsafevoidEventWithFallbackArgs(stringstr,inti,floatf,longl){this.WriteEvent(56,str,i,f,l);}

@JJamesWWang

JJamesWWang commented Nov 4, 2022

Copy link
Copy Markdown
ContributorAuthor

The two objects that can be null when calling the specific overloads in EventSource are string types and byte[] arrays, but their behavior seems inconsistent when null is passed in. Strings have a payload of an empty string, while byte arrays have a payload of an empty byte array. WriteEventVarArgs has no way of knowing whether the null was a string or byte array, so isn't it impossible to standardize the behavior? In other words, don't we have to always write either an empty string or byte array?

@davmason

Copy link
Copy Markdown
Contributor

@JJamesWWang thanks for sticking with this! Answers to your questions below.

How do I simulate the EventPipe session in a test case? I believe the following set of assertions will fail if executed while an ETW or EventPipe session is enabled (as per the failing case repro), but I'm not quite sure how to create a test under the same circumstances.

We have a test harness that automates it. See here for example:

publicstaticintMain(string[]args)
{
varproviders=newList<EventPipeProvider>()
{
newEventPipeProvider("Microsoft-DotNETCore-SampleProfiler",EventLevel.Verbose),
//ExceptionKeyword (0x8000): 0b1000_0000_0000_0000
newEventPipeProvider("Microsoft-Windows-DotNETRuntime",EventLevel.Warning,0b1000_0000_0000_0000)
};
returnIpcTraceTest.RunAndValidateEventCounts(_expectedEventCounts,_eventGeneratingAction,providers,1024);
You give it a list of providers and then the expected event count, if it runs in to an exception it will fail the test.

If you want to add a test, the easiest way is to copy and paste the .cs and .csproj and change all the names as appropriate, and then add your event.

WriteEventVarArgs has no way of knowing whether the null was a string or byte array, so isn't it impossible to standardize the behavior? In other words, don't we have to always write either an empty string or byte array?

We keep information about the parameters in the event metadata. The metadata we already have available here has a .Parameters field:

refEventMetadata metadata =refm_eventData[eventId];

You can determine what type it is by checking metadata.Parameters[i].ParameterType where i is the index of the null argument in the args array.

@JJamesWWangJJamesWWang changed the title EventListener: Add test for logging a null string in an EventSourceStandardize EventSource behavior in ETW for null valuesDec 9, 2022
@JJamesWWang

JJamesWWang commented Dec 9, 2022

Copy link
Copy Markdown
ContributorAuthor

I've added a test (ETWNullEvent.cs) for the ETW simulation, but it doesn't appear to be throwing the expected exception and is also hanging forever once the code executes. I've reduced the test to its least complex form, but I can't figure out why the test isn't working as expected. Would someone be able to take a look at this?

Expected behavior:
log.EventNullString(null, 10, 11, 12); -> This should throw an exception because it invokes the fallback event handler inside of an EventPipeSession.

Actual behavior:
log.EventNullString(null, 10, 11, 12); is executed, does not throw an exception, and then the test hangs.

@runfoapprunfoappBot mentioned this pull request Dec 12, 2022
@JJamesWWangJJamesWWang closed this by deleting the head repository Dec 25, 2022
@davmason

Copy link
Copy Markdown
Contributor

Hi @JJamesWWang,

My suspicion is that the test hanging is related to the failure, but I can't take a look since you deleted the repo you had created the pull request from.

I suspect that the test is failing, but that failure causes a hang.

@ghostghost locked as resolved and limited conversation to collaborators Feb 4, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Diagnostics.Tracingcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EventPipe] Null passed as an event argument causes crash

3 participants

@JJamesWWang@AaronRobinsonMSFT@davmason