Web Application Memory Leakage Caused by BSTR

BSTR is not like a common value type, to use it, it must be allocated and freed explicitly, otherwise will cause various memory leaking problems. There is one case I worked before, I’d like to use it explain how we found out an improper implementation of BSTR in application and how to address it.

Problem

=======

Customer has one ASP web application running on Windows 2000 SP4. They find the dllhost.exe consumes high memory (up to 1.5GB) and significantly impacts application performance.

Analysis

===================

Usually, when an ASP web application has memory problems we will suggest customer checking below points first:

1. Fix this pattern in ASP application if the web application has: “on error resume next” + loop + unhandled errors.

The statement may make the output statements unexpectedly repeated too many times in some loop code. For example:

On Error Resume Next
...
While Not RS.EOF
..

response.write RS.fields(...
..
Wend

If there are some exceptions in the above function, then RS.EOF will always return false, that means the above ASP code will enter an endless loop and consume all
available memory and make CPU high.

Please check the ASP code in Web Application to see if they have the similar coding as the above (On Error Resume Next and Loop statements).

Generally, the developers need to search “On error resume next” in search related ASP pages and comment this statement because this kind of problem may occur on
different pages if you do not use "On error resume next" carefully. If “On Error resume next” required to be used, good error handling must be used at the same time.

2. If the query result contains too much data and it uses unlimited loop to calculate/display all data, this can also cause the same
problem as you find in the environment. To avoid this, should consider optimizing such a heavy query, such as setting some accurate query conditions, query data page
by page instead of once a time. Following below article to do recordsets paging will benefit this scenario:

How To Page Through a Recordset from ASP
https://support.microsoft.com/kb/202125

This Customer is not sure with above points and this application is complicated, thus we directly enable the DebugDiag Leaktrack and then take memory dump files when problem happens. After data collection, we run the DebuDiag Memory Analysis function, can see this result:

Heap 1 - 0x00070000 consumes the most memory:

 

 

Heap 1 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Heap commited

 

In this Heap, most allocation size is 80 bytes, and the total size reaches 1 GB:

 

 

Bytes allocation 

 

Now we know to find out the root cause, we must confirm which wrong function allocated so many 80 bytes. From the debug analysis rough checking, the top suspected call and module is:

WARNING - DebugDiag was not able to locate debug symbols for FooConnector.dll, so the reported function name(s) may not be accurate.

FooConnector.dll is responsible for 87.41 MBytes worth of outstanding allocations. The following are the top 2 memory consuming functions:

FooConnector+184a: 87.12 MBytes worth of outstanding allocations.

We have no symbol files of the problematic modules, but the information told us we should check the FooConnector.dll more.

Use WinDbg to open this memory dump, let’s find some samples of the 80 bytes heap entry:

!heap -flt s 0n80

…..

        000e5af8 000b 000b [01] 000e5b00 00050 - (busy)

        000e5b50 000b 000b [01] 000e5b58 00050 - (busy)

        000e5ba8 000b 000b [01] 000e5bb0 00050 - (busy)

        000e5c00 000b 000b [01] 000e5c08 00050 - (busy)

        000e5c58 000b 000b [01] 000e5c60 00050 - (busy)

…..

Pick up one heap entry:

0:000> dc 000e5b50

000e5b50 000b000b 0008010000000044 0069004c ........D...L.i.

000e5b60 0065006e 00310020 0020003a 006e0049 n.e. .1.:. .I.n.

000e5b70 006f0063 00720072 00630065 00200074 c.o.r.r.e.c.t. .

000e5b80 00790073 0074006e 00780061 006e0020 s.y.n.t.a.x. .n.

000e5b90 00610065 00200072 00290027 002e0027 e.a.r. .'.).'...

000e5ba0 00390000 00000027 000b000b 00080100  ..9.'...........

000e5bb0 00000044 0069004c 0065006e 00310020 D...L.i.n.e. .1.

000e5bc0 0020003a 006e0049 006f0063 00720072 :. .I.n.c.o.r.r.

000b000b 00080100 is the 8 bytes of _HEAP_ENTRY header, and the information stored in this heap entry is always like:

0:000> du 000e5b50+c

000e5b5c "Line 1: Incorrect syntax near ')"

Before this string, there is always one meaningful number: 00000044.

From this information, we can recognize this is a typical BSTR structure in memory (consists of a length prefix, a data string, and a terminator). 0x44 here means the string length. Now it is clear this 80 bytes leakage is one BSTR memory leak. We can focus on checking BSTR operations in FooConnector.dll modules.

After reviewing the FooConnector project, found the BSTR leak code snippet as below:

============================

catch (_com_error &e)

            {

                        BSTR t = e.Description().copy();

                        ErrorCode = e.Error();

                        if (t != 0)

                                    ErrorDescription = t;

                        else

                                    ErrorDescription = _T("NULL");

            }

==============================

 

In the above code, it implicitly allocated BSTR by (t = _bstr_t::copy(true) ) but never freed (::SysFreeString(t)) ; Refer to:

Allocating and Releasing Memory for a BSTR

https://msdn.microsoft.com/en-us/library/xda6xzx7(VS.71).aspx

To resolve this issue, can explicitly free it (::SysFreeString(t)) or use “CComBSTR t” instead of “BSTR t”. CComBSTR is wrapper class for BSTR. This type can automatically alloc and free BSTR when CComBSTR value is out of scope. Your “ErrorDescription” is CComBSTR already in the cDatabase.h:

 

==========

 CComBSTR ErrorDescription;

===========

 

About more information for CComBSTR, refer to:

Programming with CComBSTR

https://msdn.microsoft.com/en-us/library/bdyd6xz6.aspx

Best Regards,

Freist Li