Reading “object” in memory — starting with stacks

The previous episodes started the parsing of the “nettrace” format used when contacting the .NET Diagnostics IPC server and initiate the protocol to receive CLR events. It is now time to see how to get the payload of each “object” type, especially how stacks are stored.

We have seen that the stream starts with a TraceObject that describes the rest of the stream followed by a sequence of “object”:

The remaining of each “object” is a 32 bit block size followed by the payload.

Well… not only. One thing I missed when I started to work on the nettrace format is the fact that all “object” payloads must be 4-bytes aligned on the beginning of the stream!

This is why I’m keeping track of the current position in the EventPipeSession class:

So each ParseXXXBlock function checks the minimum reader version in the header before reading the “object” payload as a memory block. The idea is being able to support backward compatibility:

The ExtractBlock function reads the size of the payload (and skips the padding if any) with ReadBlockSize:

The block name is only used for error messages if needed.

The next step is to read the payload in a memory block using these two EventPipeSession fields:

In the session constructor, _blockSize is set to 4 KB and _pBlock points to an allocated memory buffer of that size.

The rest of ExtractBlock deals with payload size: if the current payload to parse is larger than _blockSize, then these fields are updated up to a maximum of 100 KB (i.e. max block size sent by the CLR).

For debugging sake, I’m displaying each “object” payload

thanks to the DumpBuffer helper.

To ease the memory access to the memory block content, my BlockParser will be used as a base class for each dedicated parsers:

The Parse function accepts the memory buffer containing an “object” payload, its size and its position since the beginning of the stream. The derived class will have to implement the OnParse function using the ReadXXX helpers.

The two ReadVarUintXXX functions are different from the other direct read helpers because they deal with some simple compression mechanisms used by the serialization of 32-bit and 64-bit numbers.

In the different types of “object” payloads, the strings are serialized as UTF16 strings ending with a “\0” wide character. Here is the implementation of the helper function used to read a std::wstring from a memory block:

Note the check for character content in the loop: this is due to a serialization issue I will discuss later when the event “object” block will be detailed.

The Stack “object”

If you remember my previous post about retrieving call stacks for CLR events with TraceEvent, you might be wondering why there is a specific stack object since a ClrStackWalk event should contain the frames if the Stack keyword is enabled for the .NET provider. In fact, the current TraceEvent implementation is not using the stack object sent by the CLR (maybe to have the same code between ETW and EventPipe).

One stack “object” received in a nettrace stream contains one or more stacks. Each stack is identified by an id (more about this soon) and contains a list of instruction pointer addresses.

In the previous screenshot, the id of the first stack is 1 and the second is 2. In the next stack “object”, the FirstId field will be 3 and so on. This avoids storing the id in each call stack and saves space.

Note that even if this does not seem to make any sense, it might happen that the addresses list is empty.

These call stacks are stored in EventPipeSession as a per id cache:

The frames are stored as addresses in a vector:

The CLR is sending one stack per unique callstack (i.e. at least one frame is different). As you will soon see, each event “object” contains a stack id corresponding to the chain of code from which it is sent.

The next episode will detail the Metadata and Event blocks to end the series.

Resources

  • Episode 1Digging into the CLR Diagnostics IPC Protocol in C#
  • Episode 2.NET Diagnostic IPC protocol: the C++ way
  • Episode 3 CLR events: go for the nettrace file format!
  • Episode 4 Parsing the “nettrace” steam
  • Source code for the C++ implementation of CLR events listener
  • Diagnostics IPC protocol documentation

--

--

Loves to understand how things work (MVP Developer Technologies)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store