/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

   proflib.c

Abstract:

    This module contains the implementation of a rudimentry user-mode
    profiler.

Usage:

    There are 4 routines, RtlInitializeProfile, RtlStartProfile,
    RtlStopProfile, and RtlAnalyzeProfile.  To initialize profiling
    invoke RtlInitializeProfile, this routine is only called once and
    goes through the address space looking for code regions of images
    and DLLs.  To start profiling call RtlStartProfile.  To stop
    profiling call RtlStopProfile.  Note that RtlStartProfile and
    RtlStopProfile can be called repeatedly to profile only key
    "hot spots", For example:
                RtlStartProfile ();
                hot spot...
                RtlStopProfile ();
                ....
                RtlStartProfile ();
                hot spot...
                RtlStopProfile ();

    To analyze the results call RtlAnalyzeProfile.  This too can
    be called repeatedly (it stops profiling during the analysis
    phase and does NOT restart profiling).  It also does not
    zero out the values after reporting.

Author:

    Lou Perazzoli (loup) 4-Oct-1990

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <string.h>
#include <stdio.h>
#include <\nt\private\ntos\dll\ldrp.h>

NTSTATUS
InitializeKernelProfile ( VOID );

#define PAGE_SIZE 4096

typedef struct _PROFILE_BLOCK {
    HANDLE Handle;
    PVOID ImageBase;  //actual base in image header
    PULONG CodeStart;
    ULONG CodeLength;
    PULONG Buffer;
    ULONG BufferSize;
    ULONG TextNumber;
    ULONG BucketSize;
    PVOID MappedImageBase;  //actual base where mapped locally.
    PSZ ImageName;
} PROFILE_BLOCK;


#define MAX_PROFILE_COUNT 50

PROFILE_BLOCK ProfileObject[MAX_PROFILE_COUNT];

ULONG NumberOfProfileObjects = 0;
PIMAGE_DEBUG_INFO KernelDebugInfo;

//
// Image name to perform kernel mode analysis upon.
//

#define IMAGE_NAME "\\SystemRoot\\ntoskrnl.exe"

//
// Define map data file if the produced data file should be
// a mapped file (currently named "kernprof.dat").
//

// #define MAP_DATA_FILE

//
// Define map as image if the image to be profiled should be mapped
// as an image rather than as data.
//

// #define MAP_AS_IMAGE

#define MAX_PROFILE_COUNT 50

extern ULONG ProfInt;

NTSTATUS
RtlInitializeProfile (
    IN BOOLEAN KernelToo
    )

/*++

Routine Description:

    This routine initializes profiling for the current process.

Arguments:

    KernelToo - Set to TRUE if kernel code should be profiled as
                well as user code.

Return Value:

    Returns the status of the last NtCreateProfile.

--*/

{

    NTSTATUS status, LocalStatus;
    HANDLE CurrentProcessHandle;
    ULONG BufferSize;
    PVOID ImageBase;
    ULONG CodeLength;
    PULONG Buffer;
    PPEB Peb;
    PLDR_DATA_TABLE_ENTRY LdrDataTableEntry;
    PSZ ImageName;
    PLIST_ENTRY Next;
    ULONG ExportSize, DebugSize;
    PIMAGE_EXPORT_DIRECTORY ExportDirectory;
    PIMAGE_DEBUG_DIRECTORY DebugDirectory;
    PIMAGE_DEBUG_INFO DebugInfo;
    BOOLEAN PreviousPrivState;

    //
    // Locate all the executables in the address and create a
    // seperate profile object for each one.
    //

    CurrentProcessHandle = NtCurrentProcess();

    Peb = NtCurrentPeb();

    Next = Peb->Ldr->InMemoryOrderModuleList.Flink;
    while ( Next != &Peb->Ldr->InMemoryOrderModuleList) {
        LdrDataTableEntry
            = (PLDR_DATA_TABLE_ENTRY) (CONTAINING_RECORD(Next,LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks));

        ImageBase = LdrDataTableEntry->DllBase;
        if ( Peb->ImageBaseAddress == ImageBase ) {
            ImageName = "TheApplication";
        } else {
            ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)RtlImageDirectoryEntryToData(
                               ImageBase,
                               TRUE,
                               IMAGE_DIRECTORY_ENTRY_EXPORT,
                               &ExportSize);

            ImageName =  (PSZ)((ULONG)ImageBase + ExportDirectory->Name);
        }
        if (NumberOfProfileObjects > MAX_PROFILE_COUNT) {
            break;
        }

        ProfileObject[NumberOfProfileObjects].ImageBase = ImageBase;
        ProfileObject[NumberOfProfileObjects].ImageName = ImageName;
        ProfileObject[NumberOfProfileObjects].MappedImageBase = ImageBase;

        //
        // Locate the code range and start profiling.
        //

        DebugDirectory = (PIMAGE_DEBUG_DIRECTORY)RtlImageDirectoryEntryToData(
                    ImageBase, TRUE, IMAGE_DIRECTORY_ENTRY_DEBUG, &DebugSize);

        if (!DebugDirectory) {
            DbgPrint ("RtlInitializeProfile : No debug directory\n");
            return STATUS_INVALID_IMAGE_FORMAT;
        }

        DebugInfo = (PIMAGE_DEBUG_INFO)((ULONG)ImageBase + DebugDirectory->AddressOfRawData);

        ProfileObject[NumberOfProfileObjects].CodeStart = (PULONG)((ULONG)ImageBase + DebugInfo->RvaToFirstByteOfCode);
        CodeLength = (DebugInfo->RvaToLastByteOfCode - DebugInfo->RvaToFirstByteOfCode) - 1;
        ProfileObject[NumberOfProfileObjects].CodeLength = CodeLength;

        ProfileObject[NumberOfProfileObjects].TextNumber = 1;

        //
        // Analyze the size of the code and create a reasonably sized
        // profile object.
        //

        BufferSize = (CodeLength >> 1) + 4;
        Buffer = NULL;

        status = NtAllocateVirtualMemory (CurrentProcessHandle,
                                          (PVOID *)&Buffer,
                                          0,
                                          &BufferSize,
                                          MEM_RESERVE | MEM_COMMIT,
                                          PAGE_READWRITE);

        if (!NT_SUCCESS(status)) {
            DbgPrint ("alloc VM failed %lx\n",status);
            return status;
        }


        status = RtlAdjustPrivilege(
                     SE_PROF_SINGLE_PROCESS_PRIVILEGE,
                     TRUE,              //Enable
                     FALSE,             //not impersonating
                     &PreviousPrivState //Remember if it will need to be cleared
                     );

        if (!NT_SUCCESS(status) || status == STATUS_NOT_ALL_ASSIGNED) {
            DbgPrint("Enable system profile privilege failed - status 0x%lx\n",
                     status);
        }


        ProfileObject[NumberOfProfileObjects].Buffer = Buffer;
        ProfileObject[NumberOfProfileObjects].BufferSize = BufferSize;
        ProfileObject[NumberOfProfileObjects].BucketSize = 3;

        status = NtCreateProfile (
                    &ProfileObject[NumberOfProfileObjects].Handle,
                    CurrentProcessHandle,
                    ProfileObject[NumberOfProfileObjects].CodeStart,
                    CodeLength,
                    ProfileObject[NumberOfProfileObjects].BucketSize,
                    ProfileObject[NumberOfProfileObjects].Buffer ,
                    ProfileObject[NumberOfProfileObjects].BufferSize);

        if (PreviousPrivState == FALSE) {
            LocalStatus = RtlAdjustPrivilege(
                             SE_PROF_SINGLE_PROCESS_PRIVILEGE,
                             FALSE,             //Disable
                             FALSE,             //not impersonating
                             &PreviousPrivState //Don't care if it was already enabled
                             );
            if (!NT_SUCCESS(LocalStatus) || LocalStatus == STATUS_NOT_ALL_ASSIGNED) {
                DbgPrint("Disable system profile privilege failed - status 0x%lx\n",
                         LocalStatus);
            }
        }

        if (status != STATUS_SUCCESS) {
            DbgPrint("create profile %x failed - status %lx\n",
                   ProfileObject[NumberOfProfileObjects].ImageName,status);
            return status;
        }

        NumberOfProfileObjects += 1;

        Next = Next->Flink;
    }

    if (KernelToo) {

        if (NumberOfProfileObjects > MAX_PROFILE_COUNT) {
            return status;
        }
        status = InitializeKernelProfile();
    }
    return status;

}
NTSTATUS
InitializeKernelProfile (
    VOID
    )

/*++

Routine Description:

    This routine initializes profiling for the kernel for the
    current process.

Arguments:

    None.

Return Value:

    Returns the status of the last NtCreateProfile.

--*/

{

    //BUGBUG daveh I think that the new working set size calculation is
    //             generating the number of pages, when the api expects
    //             the number of bytes.

    STRING Name3;
    IO_STATUS_BLOCK IoStatus;
    HANDLE FileHandle, KernelSection;
    OBJECT_ATTRIBUTES ObjectAttributes;
    PVOID ImageBase;
    ULONG ViewSize;
    ULONG CodeLength;
    NTSTATUS status, LocalStatus;
    HANDLE CurrentProcessHandle;
    QUOTA_LIMITS QuotaLimits;
    PVOID Buffer;
    ULONG Cells;
    ULONG BucketSize;
    UNICODE_STRING Unicode;
    ULONG DebugSize;
    PVOID KernelBase;
    PIMAGE_NT_HEADERS KernelNtHeaders;
    PIMAGE_DEBUG_DIRECTORY DebugDirectory;
    BOOLEAN PreviousPrivState;

    RtlInitString (&Name3, IMAGE_NAME);
    CurrentProcessHandle = NtCurrentProcess();

    status = RtlAnsiStringToUnicodeString(&Unicode,(PANSI_STRING)&Name3,TRUE);
    ASSERT(NT_SUCCESS(status));
    InitializeObjectAttributes( &ObjectAttributes,
                                &Unicode,
                                OBJ_CASE_INSENSITIVE,
                                NULL,
                                NULL );

    //
    // Open the file as readable and executable.
    //

    status = NtOpenFile ( &FileHandle,
                          FILE_READ_DATA | FILE_EXECUTE,
                          &ObjectAttributes,
                          &IoStatus,
                          FILE_SHARE_READ,
                          0L);
    RtlFreeUnicodeString(&Unicode);

    if (!NT_SUCCESS(status)) {
        DbgPrint("open file failed status %lx\n", status);
        NtTerminateProcess(NtCurrentProcess(),STATUS_SUCCESS);
    }

    InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );

    //
    // For normal images they would be mapped as an image, but
    // the kernel has no debug section (as yet) information, hence it
    // must be mapped as a file.
    //

    status = NtCreateSection (&KernelSection,
                              SECTION_MAP_EXECUTE,
                              &ObjectAttributes,
                              0,
                              PAGE_READONLY,
                              SEC_IMAGE,
                              FileHandle);

    if (!NT_SUCCESS(status)) {
        DbgPrint("create image section failed  status %lx\n", status);
        return(status);
    }

    ViewSize = 0;

    //
    // Map a view of the section into the address space.
    //

    KernelBase = NULL;

    status = NtMapViewOfSection (KernelSection,
                                 CurrentProcessHandle,
                                 (PVOID *)&KernelBase,
                                 0L,
                                 0,
                                 NULL,
                                 &ViewSize,
                                 ViewUnmap,
                                 0,
                                 PAGE_EXECUTE);

    if (!NT_SUCCESS(status)) {
        if (status != STATUS_IMAGE_NOT_AT_BASE) {
            DbgPrint("map section status %lx base %lx size %lx\n", status,
                KernelBase, ViewSize);
        }
    }

    KernelNtHeaders = (PIMAGE_NT_HEADERS)RtlImageNtHeader(KernelBase);

    ImageBase = (PVOID)KernelNtHeaders->OptionalHeader.ImageBase;

    DebugDirectory = (PIMAGE_DEBUG_DIRECTORY)RtlImageDirectoryEntryToData(
                KernelBase, TRUE, IMAGE_DIRECTORY_ENTRY_DEBUG, &DebugSize);

    if (!DebugDirectory) {
        DbgPrint("InitializeKernelProfile : No debug directory\n");
        return STATUS_INVALID_IMAGE_FORMAT;
    }

    KernelDebugInfo = (PIMAGE_DEBUG_INFO)((ULONG)KernelBase + DebugDirectory->AddressOfRawData);
    CodeLength = (KernelDebugInfo->RvaToLastByteOfCode - KernelDebugInfo->RvaToFirstByteOfCode) -1;

    //
    // Just create a 512K byte buffer.
    //

    ViewSize = 1024 * 512;
    Buffer = NULL;

    status = NtAllocateVirtualMemory (CurrentProcessHandle,
                                      (PVOID *)&Buffer,
                                      0,
                                      &ViewSize,
                                      MEM_RESERVE | MEM_COMMIT,
                                      PAGE_READWRITE);

    if (!NT_SUCCESS(status)) {
        DbgPrint ("alloc VM failed %lx\n",status);
        NtTerminateProcess(NtCurrentProcess(),STATUS_SUCCESS);
    }

    //
    // Calculate the bucket size for the profile.
    //

    Cells = ((CodeLength / (ViewSize >> 2)) >> 2);
    BucketSize = 2;

    while (Cells != 0) {
        Cells = Cells >> 1;
        BucketSize += 1;
    }

    ProfileObject[NumberOfProfileObjects].Buffer = Buffer;
    ProfileObject[NumberOfProfileObjects].MappedImageBase = KernelBase;
    ProfileObject[NumberOfProfileObjects].BufferSize = 1 + (CodeLength >> (BucketSize - 2));
    ProfileObject[NumberOfProfileObjects].CodeStart = (PULONG)((ULONG)ImageBase + KernelDebugInfo->RvaToFirstByteOfCode);
    ProfileObject[NumberOfProfileObjects].CodeLength = CodeLength;
    ProfileObject[NumberOfProfileObjects].TextNumber = 1;
    ProfileObject[NumberOfProfileObjects].ImageBase = ImageBase;
    ProfileObject[NumberOfProfileObjects].ImageName = "ntoskrnl";
    ProfileObject[NumberOfProfileObjects].BucketSize = BucketSize;


    //
    // Increase the working set to lock down a bigger buffer.
    //

    status = NtQueryInformationProcess (CurrentProcessHandle,
                                        ProcessQuotaLimits,
                                        &QuotaLimits,
                                        sizeof(QUOTA_LIMITS),
                                        NULL );

    if (!NT_SUCCESS(status)) {
        DbgPrint ("query process info failed %lx\n",status);
        NtTerminateProcess(NtCurrentProcess(),STATUS_SUCCESS);
    }

    QuotaLimits.MaximumWorkingSetSize += ViewSize / PAGE_SIZE;
    QuotaLimits.MinimumWorkingSetSize += ViewSize / PAGE_SIZE;

    status = NtSetInformationProcess (CurrentProcessHandle,
                                  ProcessQuotaLimits,
                                  &QuotaLimits,
                                  sizeof(QUOTA_LIMITS));
    if (!NT_SUCCESS(status)) {
        DbgPrint ("setting working set failed %lx\n",status);
        return status;
    }

    status = RtlAdjustPrivilege(
                 SE_PROF_SINGLE_PROCESS_PRIVILEGE,
                 TRUE,              //Enable
                 FALSE,             //not impersonating
                 &PreviousPrivState //Remember if it will need to be cleared
                 );

    if (!NT_SUCCESS(status) || status == STATUS_NOT_ALL_ASSIGNED) {
        DbgPrint("Enable process profile privilege failed - status 0x%lx\n",
                 status);
    }

    status = NtCreateProfile (
                &ProfileObject[NumberOfProfileObjects].Handle,
                CurrentProcessHandle,
                ProfileObject[NumberOfProfileObjects].CodeStart,
                CodeLength,
                ProfileObject[NumberOfProfileObjects].BucketSize,
                ProfileObject[NumberOfProfileObjects].Buffer ,
                ProfileObject[NumberOfProfileObjects].BufferSize);

    if (PreviousPrivState == FALSE) {
        LocalStatus = RtlAdjustPrivilege(
                         SE_PROF_SINGLE_PROCESS_PRIVILEGE,
                         FALSE,             //Disable
                         FALSE,             //not impersonating
                         &PreviousPrivState //Don't care if it was already enabled
                         );
        if (!NT_SUCCESS(LocalStatus) || LocalStatus == STATUS_NOT_ALL_ASSIGNED) {
            DbgPrint("Disable system profile privilege failed - status 0x%lx\n",
                     LocalStatus);
        }
    }

    if (status != STATUS_SUCCESS) {
        DbgPrint("create kernel profile %s failed - status %lx\n",
                   ProfileObject[NumberOfProfileObjects].ImageName,status);
    }

    NumberOfProfileObjects += 1;

    return status;
}


VOID
RtlpWriteProfileLine(
    IN HANDLE ProfileHandle,
    IN PSZ Line,
    IN int nbytes
    )
{
    IO_STATUS_BLOCK IoStatusBlock;

    NtWriteFile(
        ProfileHandle,
        NULL,
        NULL,
        NULL,
        &IoStatusBlock,
        Line,
        (ULONG)nbytes,
        NULL,
        NULL
        );

}


HANDLE
RtlpOpenProfileOutputFile()
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    UNICODE_STRING FileName;
    IO_STATUS_BLOCK IoStatusBlock;
    BOOLEAN TranslationStatus;
    RTL_RELATIVE_NAME RelativeName;
    PVOID FreeBuffer;


    TranslationStatus = RtlDosPathNameToNtPathName_U(
                            L"\\profile.out",
                            &FileName,
                            NULL,
                            &RelativeName
                            );

    if ( !TranslationStatus ) {
        return NULL;
        }
    FreeBuffer = FileName.Buffer;

    if ( RelativeName.RelativeName.Length ) {
        FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
        }
    else {
        RelativeName.ContainingDirectory = NULL;
        }

    InitializeObjectAttributes(
        &Obja,
        &FileName,
        OBJ_CASE_INSENSITIVE,
        RelativeName.ContainingDirectory,
        NULL
        );

    Status = NtCreateFile(
                &Handle,
                FILE_APPEND_DATA | SYNCHRONIZE,
                &Obja,
                &IoStatusBlock,
                NULL,
                FILE_ATTRIBUTE_NORMAL,
                FILE_SHARE_READ,
                FILE_OPEN_IF,
                FILE_SYNCHRONOUS_IO_NONALERT,
                NULL,
                0L
                );

    RtlFreeHeap(RtlProcessHeap(),0,FreeBuffer);
    if ( !NT_SUCCESS(Status) ) {
        return NULL;
        }

    return Handle;
}

VOID
RtlpDeleteProfileOutputFile()
{
    NTSTATUS Status;
    OBJECT_ATTRIBUTES Obja;
    HANDLE Handle;
    UNICODE_STRING FileName;
    IO_STATUS_BLOCK IoStatusBlock;
    FILE_DISPOSITION_INFORMATION Disposition;
    BOOLEAN TranslationStatus;
    RTL_RELATIVE_NAME RelativeName;
    PVOID FreeBuffer;

    TranslationStatus = RtlDosPathNameToNtPathName_U(
                            L"\\profile.out",
                            &FileName,
                            NULL,
                            &RelativeName
                            );

    if ( !TranslationStatus ) {
        return;
        }

    FreeBuffer = FileName.Buffer;

    if ( RelativeName.RelativeName.Length ) {
        FileName = *(PUNICODE_STRING)&RelativeName.RelativeName;
        }
    else {
        RelativeName.ContainingDirectory = NULL;
        }

    InitializeObjectAttributes(
        &Obja,
        &FileName,
        OBJ_CASE_INSENSITIVE,
        RelativeName.ContainingDirectory,
        NULL
        );

    //
    // Open the file for delete access
    //

    Status = NtOpenFile(
                &Handle,
                (ACCESS_MASK)DELETE | SYNCHRONIZE,
                &Obja,
                &IoStatusBlock,
                FILE_SHARE_DELETE,
                FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE
                );
    RtlFreeHeap(RtlProcessHeap(),0,FreeBuffer);
    if ( !NT_SUCCESS(Status) ) {
        return;
        }

    //
    // Delete the file
    //
    Disposition.DeleteFile = TRUE;

    Status = NtSetInformationFile(
                Handle,
                &IoStatusBlock,
                &Disposition,
                sizeof(Disposition),
                FileDispositionInformation
                );

    NtClose(Handle);
}




NTSTATUS
RtlStartProfile (
    VOID
    )
/*++

Routine Description:

    This routine starts all profile objects which have been initialized.

Arguments:

    None.

Return Value:

    Returns the status of the last NtStartProfile.

--*/

{
    ULONG i;
    NTSTATUS status;
    QUOTA_LIMITS QuotaLimits;

    NtSetIntervalProfile(ProfInt);
    RtlpDeleteProfileOutputFile();

    for (i = 0; i < NumberOfProfileObjects; i++) {

        status = NtStartProfile (ProfileObject[i].Handle);

        if (status == STATUS_WORKING_SET_QUOTA) {

           //
           // Increase the working set to lock down a bigger buffer.
           //

           status = NtQueryInformationProcess (NtCurrentProcess(),
                                               ProcessQuotaLimits,
                                               &QuotaLimits,
                                               sizeof(QUOTA_LIMITS),
                                               NULL );

           if (!NT_SUCCESS(status)) {
               DbgPrint ("query process info failed %lx\n",status);
               return status;

           }

           QuotaLimits.MaximumWorkingSetSize +=
                 10 * PAGE_SIZE + ProfileObject[i].BufferSize;
           QuotaLimits.MinimumWorkingSetSize +=
                 10 * PAGE_SIZE + ProfileObject[i].BufferSize;

           status = NtSetInformationProcess (NtCurrentProcess(),
                                         ProcessQuotaLimits,
                                         &QuotaLimits,
                                         sizeof(QUOTA_LIMITS));
           if (!NT_SUCCESS(status)) {
               DbgPrint ("setting working set failed %lx\n",status);
               return status;
           }
           status = NtStartProfile (ProfileObject[i].Handle);
        }

        if (status != STATUS_SUCCESS) {
            DbgPrint("start profile %s failed - status %lx\n",
                ProfileObject[i].ImageName, status);
            return status;
        }
    }
    return status;
}
NTSTATUS
RtlStopProfile (
    VOID
    )

/*++

Routine Description:

    This routine stops all profile objects which have been initialized.

Arguments:

    None.

Return Value:

    Returns the status of the last NtStopProfile.

--*/

{
    ULONG i;
    NTSTATUS status;

    for (i = 0; i < NumberOfProfileObjects; i++) {
        status = NtStopProfile (ProfileObject[i].Handle);
        if (status != STATUS_SUCCESS) {
            DbgPrint("stop profile %s failed - status %lx\n",
                   ProfileObject[i].ImageName,status);
            return status;
        }
    }
    return status;
}

NTSTATUS
RtlAnalyzeProfile (
    VOID
    )

/*++

Routine Description:

    This routine does the analysis of all the profile buffers and
    correlates hits to the appropriate symbol table.

Arguments:

    None.

Return Value:

    None.

--*/

{


    RTL_SYMBOL_INFORMATION ThisSymbol;
    RTL_SYMBOL_INFORMATION LastSymbol;
    ULONG CountAtSymbol;
    NTSTATUS Status;
    ULONG Va;
    HANDLE ProfileHandle;
    CHAR Line[512];
    int i,n;
    PULONG Buffer, BufferEnd, Counter;


    ProfileHandle = RtlpOpenProfileOutputFile();
    ASSERT(ProfileHandle);

    for (i = 0; i < NumberOfProfileObjects; i++) {
        Status = NtStopProfile (ProfileObject[i].Handle);
        }


    //
    // The new profiler
    //

    for (i = 0; i < NumberOfProfileObjects; i++)  {

        LastSymbol.Value = 0;
        CountAtSymbol = 0;

        //
        // Sum the total number of cells written.
        //

        BufferEnd = ProfileObject[i].Buffer + (
                    ProfileObject[i].BufferSize / sizeof(ULONG));
        Buffer = ProfileObject[i].Buffer;

        for ( Counter = Buffer; Counter < BufferEnd; Counter += 1 ) {
            if ( *Counter ) {

                //
                // Now we have an an address relative to the buffer
                // base.
                //

                Va = (ULONG)((PUCHAR)Counter - (PUCHAR)Buffer);
                Va = Va * ( 1 << (ProfileObject[i].BucketSize - 2));

                //
                // Add in the image base and the base of the
                // code to get the Va in the image
                //

                Va = Va + (ULONG)ProfileObject[i].CodeStart;

                Status = RtlLookupSymbolByAddress(
                            ProfileObject[i].ImageBase,
                            NULL,
                            (PVOID)Va,
                            0x4000,
                            &ThisSymbol,
                            NULL
                            );
                if ( NT_SUCCESS(Status) ) {
                    if ( LastSymbol.Value && LastSymbol.Value == ThisSymbol.Value ) {
                        CountAtSymbol += *Counter;
                    }
                    else {
                        if ( LastSymbol.Value ) {
                            if ( CountAtSymbol ) {
                                n= sprintf(Line,"%d,%s,%S\n",
                                    CountAtSymbol,
                                    ProfileObject[i].ImageName,
                                    &LastSymbol.Name
                                    );
                                RtlpWriteProfileLine(ProfileHandle,Line,n);
                            }
                        }
                        CountAtSymbol = *Counter;
                        LastSymbol = ThisSymbol;
                    }
                }
            }
        }
        if ( CountAtSymbol ) {
            n= sprintf(Line,"%d,%s,%S\n",
                CountAtSymbol,
                ProfileObject[i].ImageName,
                &LastSymbol.Name
                );
            RtlpWriteProfileLine(ProfileHandle,Line,n);
        }
    }

    for (i = 0; i < NumberOfProfileObjects; i++) {
        Buffer = ProfileObject[i].Buffer;
        RtlZeroMemory(Buffer,ProfileObject[i].BufferSize);
    }
    NtClose(ProfileHandle);
    return Status;
}
