How to extract function argument and return value through .NET profiler API ? RRS feed

  • Question

  • Through Profiler Api we can track the current methods and their performance. but can we do capture the function argument parameter and their return value as also?

    Suppose below is a use case :


      public void FatDateNow()
                DateTime dt = DateTime.Now;
                HttpClient http = new HttpClient();
                http.BaseAddress = new Uri("");
                http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                AccountAPIInputParam input = new AccountAPIInputParam();
                input.AccountId = string.IsNullOrWhiteSpace(null) ? "d2b5ec99-8123-3d20-866a-3d7d203b5479" : null;
                var d = JsonConvert.SerializeObject(input);
                var content = new StringContent(d, Encoding.UTF8, "application/json");

                    var output = http.PostAsync("api/v1.0/getAccountLibrary", new StringContent("test"));
                    var url = output.Result.RequestMessage.RequestUri;

                    //InterceptLib.DebugLogger.Log(http.BaseAddress + http.)
                catch (Exception ex)


                System.Console.Write("AKS -DateTime.Now : ");

    Above is C# method and now i want to capture url variable or  RequestUri from above method in run time. How can we do that ? Can we do through FunctionEnter/FunctionLeave api of profiler.If yes then please show me with some code snippet to achieve that. Thanks Ashuosh

    • Edited by Ashutosh K S Monday, July 30, 2018 2:26 PM grammer
    Monday, July 30, 2018 2:22 PM

All replies

  • Yes.

    Include COR_PRF_ENABLE_FUNCTION_ARGS in your call to SetEventMask and register the callbacks using SetEnterLeaveFunctionHooks3WithInfo. Your ELT callbacks will receive a COR_PRF_ELT_INFO as one of their arguments, you can pass that to GetFunctionEnter3Info and GetFunctionLeave3Info to get the COR_PRF_FUNCTION_ARGUMENT_RANGE structure which describes a contiguous block of memory where you can find the values.

    Monday, July 30, 2018 10:03 PM
  • Thanks for reply!

    I am using ICorProfilerInfo2  as below for invoking ELT hooks.

     m_corProfilerInfo2->SetEnterLeaveFunctionHooks2(MyFunctionEnter, MyFunctionLeave, NULL);

    SetEventmask has been already set as below

       //eventMask |= COR_PRF_MONITOR_THREADS; // create / delete thread
       eventMask |= COR_PRF_ENABLE_FRAME_INFO;

    here is my functionLeave2 asm declaration :

    _declspec(naked) void __stdcall MyFunctionLeave(FunctionID funcId,UINT_PTR clientData,COR_PRF_FRAME_INFO func,COR_PRF_FUNCTION_ARGUMENT_RANGE *retvalRange
    _asm {
    push ebp
    mov ebp, esp
    mov   eax, [ebp + 0x14]   //argumentInfo
    push  eax
    mov   ecx, [ebp + 0x10]   //func
    push  ecx
    mov   edx, [ebp + 0x0C]   //clientData
    push  edx
    mov   eax, [ebp + 0x08]   //funcID
    push  eax
    call MyFunctionLeave2
    pop ebp
    ret 16

    below is  ELT hook function(MyFunctionLeave2)

    void __stdcall MyFunctionLeave2(FunctionID functionID,UINT_PTR   clientData,COR_PRF_FRAME_INFO  func,COR_PRF_FUNCTION_ARGUMENT_INFO   *argumentInfo)

    ///Trying to get the function return and input parameter from *argumentInfo ????


    I am trying to get the function argument and return values here from argumentInfo parameter. 

    Would you please guide/help me on that whether by above approach can i get the function return and input parameter or i need to switch on ICorProfilerInfo3.



    Ashutosh Kumar Singh

    Tuesday, July 31, 2018 9:55 AM
  • SetEnterLeaveFunctionHooks2 is the old .NET 2 way, but should still work fine.

    IIRC, the arguments are only available from the enter callback and (obviously) the result is only available from the leave callback. Though to get the result you will also need to pass COR_PRF_ENABLE_FUNCTION_RETVAL into the SetEventMask call.

    Tuesday, July 31, 2018 1:54 PM
  • Appreciate your quick response Brian!<o:p></o:p>

    I have already used  COR_PRF_ENABLE_FUNCTION_RETVAL event mask in SetEventMask function. I am facing the challenge of extracting the values of function input parameter or return parameter here.<o:p></o:p>

    Once I reach inside the functionLeave2, then how we can  extract the parameter/return value of a desired function?<o:p></o:p>

    Ashutosh Kumar Singh

    Tuesday, July 31, 2018 2:31 PM
  • You can't get the arguments from the function leave callback, you will need to collect them in the function enter callback and record them somewhere.

    The argument values and return value should be in the ranges specified by the COR_PRF_FUNCTION_ARGUMENT_RANGE structures (in the case of the enter callback, via the COR_PRF_FUNCTION_ARGUMENT_INFO structure). You would need to parse the method signature to determine how to interpret the data though (eg. if the return value is an int, then I would expect the range to contain the 4 bytes of the int; but if the return type is an object, then the range would contain the 4/8 byte ObjectID).

    Note: if you capture an ObjectID and expect to use it later, you will need to register for GC callbacks and update your references when the GC moves the objects.

    Tuesday, July 31, 2018 9:57 PM
  • Thanks Brian!

    It means we should extract the input parameter in FunctionEnter2 and for  function return value we need to focus the FunctionLeave2?

    I was trying  through a common function as below :

    In my above problem case it is coming the value as class(ELEMENT_TYPE_CLASS) but it is not able to find neither input/return value. I am not sure what  i am missing here?

    void ILRewriteProfilerImpl::WriteMethodInfo(FunctionID functionId, COR_PRF_FUNCTION_ARGUMENT_INFO *pArgInfo)

    IMetaDataImport *pMetaDataImport;
    PCCOR_SIGNATURE pSig, p, pParamStart;
    WCHAR           szName[256], szType[256], szValue[256], szBuf[1024];
    ULONG           i;
    CorElementType  type;
    mdMethodDef     methodDef;
    mdTypeDef       typeDef;
    BOOL            bArray, bRef;
    LPVOID          *lplpValue;
    mdParamDef       paramDef;
    LPWSTR           szParamNAme;

    g_debugLogger << "WriteMethodInfo" << std::endl;

    m_corProfilerInfo2->GetTokenAndMetaDataFromFunction(functionId, IID_IMetaDataImport, (IUnknown **)&pMetaDataImport, &methodDef);
    pMetaDataImport->GetMethodProps(methodDef, NULL, szName, 256, NULL, NULL, &pSig, NULL, NULL, NULL);

    p = &pSig[2]; //type = GetElementType(&p, &typeDef, &bRef, &bArray);
    pParamStart = p;

    g_debugLogger << "WriteMethodInfo - function Name" << szName << std::endl;
    g_debugLogger << "numRanges = " << pArgInfo->numRanges <<"totalArgumentsize " <<pArgInfo->totalArgumentSize << std::endl;
    g_debugLogger << "WriteLogFile(szBuf);" <<std::endl;

    p = pParamStart;

    for (i = 0; i < pArgInfo->numRanges; i++) {
    lplpValue = (LPVOID *)pArgInfo->ranges[i].startAddress;

    type = GetElementType(&p, &typeDef, &bRef, &bArray);
    GetElementTypeName(pMetaDataImport, type, typeDef, bRef, bArray, szType);

    g_debugLogger << "GetElementTypeName called" << std::endl;

    if (bArray) {

    g_debugLogger << "bArray = true " <<  std::endl;
    GetArray(lplpValue, type, szValue);
    wsprintfW(szBuf, L"%s = %s", szType, szValue);
    g_debugLogger << "bArray " << szType <<" "<< szValue << std::endl;
    else if (type == ELEMENT_TYPE_CLASS) {
    g_debugLogger << "bArray = ELEMENT_TYPE_CLASS " << std::endl;

    ClassID  classId;
    g_debugLogger << "Going to convert in objectID" << std::endl;
    ObjectID oid = *(ObjectID *)(lplpValue);

    g_debugLogger << "ObjectID oid = *(ObjectID *)(lplpValue); " << std::endl;
    m_corProfilerInfo2->GetClassFromObject(oid, &classId);
    g_debugLogger << "_corProfilerInfo2->GetClassFromObject(oid, &classId) " << std::endl;

    GetClass(pMetaDataImport, classId, szType, *lplpValue);

    g_debugLogger << "GetClass finished" << szType << " " << classId << std::endl;

    else if (type == ELEMENT_TYPE_VALUETYPE) {

    g_debugLogger << "bArray = ELEMENT_TYPE_VALUETYPE " << std::endl;
    ClassID  classId;
    ModuleID moduleId;

    m_corProfilerInfo2->GetFunctionInfo(functionId, 0, &moduleId, 0);
    m_corProfilerInfo2->GetClassFromToken(moduleId, typeDef, &classId);

    GetClass(pMetaDataImport, classId, szType, lplpValue);

    else {

    g_debugLogger << "bArray = else  " << std::endl;

    GetValue(bRef ? *lplpValue : lplpValue, type, szValue);
    //wsprintfW(szBuf, L"%s = %s", szType, szValue);

    g_debugLogger << "wsprintfW(szBuf, L, szType, szValue);" << szType<< "  "<<szValue <<std::endl;


    g_debugLogger << "pMetaDataImport->Release();" << std::endl;



    void ILRewriteProfilerImpl::GetClass(IMetaDataImport *pMetaDataImport, ClassID classId, LPWSTR lpszClassName, LPVOID lpValue)
    g_debugLogger << "GetClass -start = " << std::endl;

    WCHAR            szBuf[1024];
    LPBYTE           lpAddress = (LPBYTE)lpValue;
    ULONG            i, uFieldOffsetCount = 0;
    COR_FIELD_OFFSET *pFieldOffset;

    m_corProfilerInfo2->GetClassLayout(classId, NULL, 0, &uFieldOffsetCount, NULL);
    if (uFieldOffsetCount == 0)
    g_debugLogger << "uFieldOffsetCount == 0" << std::endl;

    pFieldOffset = (COR_FIELD_OFFSET *)HeapAlloc(GetProcessHeap(), 0, sizeof(COR_FIELD_OFFSET) * uFieldOffsetCount);
    m_corProfilerInfo2->GetClassLayout(classId, pFieldOffset, uFieldOffsetCount, &uFieldOffsetCount, NULL);

    g_debugLogger << "for (i = 0; i < uFieldOffsetCount; i++) {" << std::endl;

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

    g_debugLogger << "Start loop -uFieldOffsetCount" << uFieldOffsetCount<< std::endl;

    WCHAR           szName[256], szType[256], szValue[256];
    CorElementType  type;
    BOOL            bArray, bRef;
    mdTypeDef       typeDef;

    pMetaDataImport->GetFieldProps(pFieldOffset[i].ridOfField, NULL, szName, 256, NULL, NULL, &pSig, NULL, NULL, NULL, NULL);

    p = &pSig[1];
    type = GetElementType(&p, &typeDef, &bRef, &bArray);
    GetElementTypeName(pMetaDataImport, type, typeDef, bRef, bArray, szType);

    if (bArray)
    GetArray((LPVOID)(lpAddress + pFieldOffset[i].ulOffset), type, szValue);
    GetValue((LPVOID)(lpAddress + pFieldOffset[i].ulOffset), type, szValue);

    wsprintfW(szBuf, L"%s.%s %s = %s", lpszClassName, szType, szName, szValue);

    g_debugLogger << "End loop -szType - szName - szValue " << szType << " "<<szName <<"  "<< szValue <<std::endl;

    HeapFree(GetProcessHeap(), 0, pFieldOffset);

    g_debugLogger << "GetClass -End = " << std::endl;

    Ashutosh Kumar Singh

    Friday, August 3, 2018 5:39 AM
  • Would be very helpful if @Brian can comment/help/guide us. If anyone has expertise in this area then please help me out.

    Ashutosh Kumar Singh

    Thursday, August 16, 2018 1:21 PM

  • Hooking the functions is a good approach but it will eat up the application's performance. Best approach is instrumenting the methods and get whatever u want. 

    • Edited by Selva VS Wednesday, August 29, 2018 11:14 AM
    Wednesday, August 29, 2018 11:13 AM
  • Thanks!

    Would be very helpful if it can be achieved by .NET instrumentation.

    Can you point some strategy/sample or api to start digging in to that.


    Ashutosh Kumar Singh

    Monday, November 26, 2018 1:42 PM

    Here u get the sample code with some very basic functionality. Build it , deploy the dll and enjoy instrumenting. 

    Tuesday, November 27, 2018 4:32 AM
  • I think, we are talking the same thing.It is just a naming convention whether you are referring it, hooking or instrumentation.

    Ashutosh Kumar Singh

    Tuesday, November 27, 2018 10:54 AM
  • Both are different terms. Please refer to documents by David Broman. Linking one here

    Are you looking to build any tool like profiling .NET applications?


    Tuesday, November 27, 2018 11:12 AM