internal override void Process(ProfilerSession sess)
        {
            Tracing.PacketTrace("<<< GARBAGE COLLECTION BEGIN");

            sess.HeapBytesFree = m_freeBytes;

            GarbageCollectionEnd gc = new GarbageCollectionEnd();
            gc.liveObjects = new List<uint>(sess.m_liveObjectTable);
            sess.AddEvent(gc);
        }
        internal void ProcessEvent(_PRF.ProfilerSession ps, _PRF.ProfilerEvent pe)
        {
            ulong callstack;

            if (m_processedFirstEvent == false)
            {
                m_processedFirstEvent = true;
                m_startTime           = pe.Time;
                m_lastWrittenTime     = pe.Time;
            }
            else
            {
                if (pe.Time >= m_lastWrittenTime + 1)
                {   // Mimic the 5-ms granularity of the CLRProfiler.
                    m_sw.WriteLine("i {0}", pe.Time - m_startTime);
                    m_lastWrittenTime = pe.Time;
                }
            }

            switch (pe.Type)
            {
            case _PRF.ProfilerEvent.EventType.HeapDump:
            {
                _PRF.HeapDump hd = (_PRF.HeapDump)pe;
                foreach (_PRF.HeapDumpRoot hdr in hd.RootTable)
                {
                    uint md = 0;

                    if (hdr.m_type == HeapDumpRoot.RootType.Stack)
                    {
                        /* This needs to come first because it has the side effect of writing a
                         * method definition out if one doesn't exist for it yet.
                         */
                        md = FindMethod(ps, hdr.m_method);
                    }

                    m_sw.Write("e 0x{0:x8} ", hdr.m_address);
                    switch (hdr.m_type)
                    {
                    case HeapDumpRoot.RootType.Finalizer:
                        m_sw.Write("2 ");
                        break;

                    case HeapDumpRoot.RootType.Stack:
                        m_sw.Write("1 ");
                        break;

                    default:
                        m_sw.Write("0 ");
                        break;
                    }
                    switch (hdr.m_flags)
                    {
                    case HeapDumpRoot.RootFlags.Pinned:
                        m_sw.Write("1 ");
                        break;

                    default:
                        m_sw.Write("0 ");
                        break;
                    }
                    switch (hdr.m_type)
                    {
                    case HeapDumpRoot.RootType.Stack:
                        m_sw.WriteLine("{0}", md);
                        break;

                    default:
                        m_sw.WriteLine("0");
                        break;
                    }
                }
                foreach (_PRF.HeapDumpObject hdo in hd.ObjectTable)
                {
                    ulong typeid = FindType(ps, hdo.m_type);

                    m_sw.Write("o 0x{0:x8} {1} {2}", hdo.m_address, typeid, hdo.m_size);
                    if (hdo.m_references != null)
                    {
                        foreach (uint ptr in hdo.m_references)
                        {
                            m_sw.Write(" 0x{0:x8}", ptr);
                        }
                    }
                    m_sw.WriteLine();
                }
                break;
            }

            case _PRF.ProfilerEvent.EventType.Call:
            {
                _PRF.FunctionCall f = (_PRF.FunctionCall)pe;
                callstack = MakeCallStack(ps, f.m_callStack);

                m_sw.WriteLine("c {0} {1}", f.m_thread, callstack);
                break;
            }

            case _PRF.ProfilerEvent.EventType.Return:
            case _PRF.ProfilerEvent.EventType.ContextSwitch:
                //The CLRProfiler does not care about function timing; it's primary goal is to display memory usage over time, and barely considers function calls
                break;

            case _PRF.ProfilerEvent.EventType.Allocation:
            {
                _PRF.ObjectAllocation a = (_PRF.ObjectAllocation)pe;
                callstack = MakeCallStack(ps, a.m_callStack, a.m_objectType, a.m_size);
                m_sw.WriteLine("! {0} 0x{1:x} {2}", a.m_thread, a.m_address, callstack);
                break;
            }

            case _PRF.ProfilerEvent.EventType.Relocation:
            {
                _PRF.ObjectRelocation r = (_PRF.ObjectRelocation)pe;
                for (uint i = 0; i < r.m_relocationRegions.Length; i++)
                {
                    m_sw.WriteLine("u 0x{0:x} 0x{1:x} {2}", r.m_relocationRegions[i].m_start,
                                   r.m_relocationRegions[i].m_start + r.m_relocationRegions[i].m_offset,
                                   r.m_relocationRegions[i].m_end - r.m_relocationRegions[i].m_start);
                }
                break;
            }

            case _PRF.ProfilerEvent.EventType.Deallocation:
            {
                _PRF.ObjectDeletion d = (_PRF.ObjectDeletion)pe;
                break;
            }

            case _PRF.ProfilerEvent.EventType.GarbageCollectionBegin:
            {
                _PRF.GarbageCollectionBegin gc = (_PRF.GarbageCollectionBegin)pe;
                uint lastObjAddress            = ps.m_liveObjectTable[ps.m_liveObjectTable.Count - 1] + 1;
                m_sw.WriteLine("b 1 0 0 0x{0:x} {1} {2} 0", ps.HeapStart, lastObjAddress, ps.HeapBytesReserved);
                break;
            }

            case _PRF.ProfilerEvent.EventType.GarbageCollectionEnd:
            {
                _PRF.GarbageCollectionEnd gc = (_PRF.GarbageCollectionEnd)pe;
                for (int i = 0; i < gc.liveObjects.Count; i++)
                {
                    //Send length of 1 for single object, regardless of true object length.
                    m_sw.WriteLine("v 0x{0:x} 1", gc.liveObjects[i]);
                }
                uint lastObjAddress = ps.m_liveObjectTable[ps.m_liveObjectTable.Count - 1] + 1;
                m_sw.WriteLine("b 0 0 0 0x{0:x} {1} {2} 0", ps.HeapStart, lastObjAddress, ps.HeapBytesReserved);
                break;
            }

            case _PRF.ProfilerEvent.EventType.HeapCompactionBegin:
            {
                _PRF.HeapCompactionBegin gc = (_PRF.HeapCompactionBegin)pe;
                uint lastObjAddress         = ps.m_liveObjectTable[ps.m_liveObjectTable.Count - 1] + 1;
                m_sw.WriteLine("b 1 0 0 0x{0:x} {1} {2} 0", ps.HeapStart, lastObjAddress, ps.HeapBytesReserved);
                break;
            }

            case _PRF.ProfilerEvent.EventType.HeapCompactionEnd:
            {
                _PRF.HeapCompactionEnd gc = (_PRF.HeapCompactionEnd)pe;
                uint lastObjAddress       = ps.m_liveObjectTable[ps.m_liveObjectTable.Count - 1] + 1;
                m_sw.WriteLine("b 0 0 0 0x{0:x} {1} {2} 0", ps.HeapStart, lastObjAddress, ps.HeapBytesReserved);
                break;
            }
            }
            m_sw.Flush();
        }