Debug Tutorial Part 3: The Heap

【信息科学与工程学】【数据中心】第三十五篇 云计算数据中心的学科知识04 编号学科(课程)核心知识点在云计算/云存储/云网络/云安全/云MaaS中的作用代表教材/资料/论文 + 数学方程式列表工业界应用D1421​云原生数据库:TiDB​HTAP(混合事务/分析处理)、分布式SQL、水平扩展、强一致(Raft)、自动故障恢复、与MySQL兼容、TiFlash列式引擎提供弹性扩展的分布式数据库;支撑高并发在线交易与实时分析教材:《TiDB in Action》PingCAP(2020) 论文:《TiDB: A Raft-based HTAP Database》(2017) 阅读详情

Introduction

In the previous installment of this debug series, we learned about the stack. The stack is a temporary storage location for local variables, parameters, return addresses, and just about anything the compiler wants to use it for. In this installment of the debug series, we will learn about the heap in usermode.

What's the heap?

The heap is just a block of memory within the process space that can be used to provide memory to an application when requested. The system APIs generally allocate a block of memory and serve it to the application as requested. The APIs add a header to each requested memory location that helps the system mark it as in-use and its size. This helps the system when it frees the memory. (Global variables are also on the heap).

The methods I am talking about above are completely in usermode. This block of memory is already allocated into your process space for your application to use. This chunk of memory is allocated when the specific library starts, for example in the DllMain() of MSVCRT.DLL. The code that handles malloc() generally works of this already allocated chunk of memory.

Memory heaps can be allocated into your process using functions such as HeapCreate. This function allocates a heap segment and returns you the segment number. Your application can then pass this segment into HeapAlloc to allocate memory from this heap. This works the same way as I described malloc as working, the Heap* functions perform all management of memory on the segment you allocated. This is actually what malloc() uses internally. A segment is allocated specifically for malloc() function calls.

There is another function termed VirtualAlloc. This function allocates memory on a larger scale for application use and commits the pages to memory. This function does not use a pre-allocated heap such as HeapAlloc does (the one you supply from HeapCreate) and you can even specify the location of the memory, but it's not required. This is a more advanced method of allocation and generally is not used nor needed in most application programs.

This is one of the main reasons you cannot free memory with a different function pair than you allocated (i.e., allocate with LocalAlloc() and free with C's free()). This is because they could have been allocated using different methods or different underlying heaps. The underlying code is generally stupid and usually attempts to free it anyway causing corruption in memory. This is also why any module that allocates memory should always free it. What if you allocate memory in one DLL and free in another? It may work some of the time, but maybe, one day you throw a debug DLL to replace one of them. I guarantee you will trap, as the debug heap allocations will contain different information than the release allocations! That's a bad habit! Allocate and free in the same module using the matching function pair!

Allocated Memory is not zero?

I mentioned this in the last tutorial that you may see values on the stack that don't make sense such as return values to function calls that currently haven't happened and etc. The stack is generally initialized to zero, however, during its use it becomes dirty. You know that local variables aren't always initialized, so if you make a stack call, those values aren't reset to zero when the stack moves up. If you pop a value off the stack, the stack may decrement but the values stay unless they are physically cleaned up. Sometimes the stack optimizes things and doesn't clean up variables as well. So, seeing "ghost" values on the stack is very common. The same thing can occur with the heap.

Freeing a location on the heap does not clear its value to zero unless you have done so before you freed the memory. So, it's very common that after allocating memory, your pointer will point to "garbage". This is generally why you usually initialize memory to zero after you've allocated it.

It may seem funny to "zero memory" before freeing it, however, if your application contains sensitive information such as passwords, or the like, it's probably best to zero out even stack variables before returning. This helps erase the sensitive information after their use. You wouldn't want your program to trap, and right there on the stack or on the heap is the user's password!

Heap Problems

In this article, I will go over the two most common problems with the heap. These are "Memory Leaks" and "Heap Corruption".

Memory Leaks

If you've ever noticed that over time you eventually start to see your program's memory foot print grow, using Task Manager, you may realize you have a memory leak. A memory leak basically means that you are allocating memory and not freeing it. The "leak" really occurs when the program "forgets" or drops the allocated memory into space never to reference it or even remember it's there all together. This is when we start the actual "leak". The memory footprint of a process can grow without leaking, the program may just require a lot of memory. So, where do we begin?

The first thing I would do is check task manager. If it's a fast leak, the memory may go up very fast. If it's a slow leak, the memory may go up gradually over time. First, you need to know how to read task manager.

  1. Virtual Memory - This field is how much memory is unique to this process. If the process was to be paged out to disk, this is how much it would take up in the paging file.
  2. Memory Usage - This is the "working set" or how much memory is in physical memory. Some people get confused when they see this number bigger than the Virtual Memory size. This is because this number does not just include memory "unique" to this process but also includes memory that is shared by other processes. For example, a lot of processes use kernel32.dll and to duplicate the same code for every process would be wasteful.

So, once you have determined the leak, you need to find the problem. Heap problems are some of the hardest problems to track down. One thing that can help you is that, if it is a leak, most likely they came from the same source. Even if there are multiple sources, they all will leak a lot of data. This means that usually the case is that the stored data will all be similar.

If the stored data is similar, you just need to find any memory that has been allocated in large amounts and examine it. To find the data, here are some tips:

  1. Generally, memory allocated from the same place are "usually" the same size. This is true for fixed structures, so you need to find allocations that are many and of similar size. This does not always have to be the case, for example, dynamically allocated strings based on a varying size.
  2. The memory allocated from the same place generally will contain the same or similar information or type of information. This means once you find memory, examine it. Compare different memory locations and see if they are similar. If you know data structures, etc., you could verify the size and match the data in the memory to attempt to come up with an educated "guess" as to what the memory actually is. This can then help narrow down where to look for such memory allocations in your code.

I have written a small program that causes a memory leak. This program is very simple and we will go through a simple scenario. This will help to get you acquainted with the heap.

Now, when we see a memory leak in Windows in your program, it doesn't always mean it's from your application. If you use a third party library or DLL, perhaps they're leaking the memory. Perhaps if you see a memory leak, but you're not directly "allocating" memory, then something you are doing could be indirectly allocating memory. For example, say you want to open a registry key and you use RegOpenKey. Do you know how that's implemented? No. So, if you leaked the "handle", could there be some memory associated with it? There could be. "Handle" leaks are another problem I will deal with in a later tutorial.

Now, I'm not saying that leaking a registry key would cause memory to grow and I'm not saying that leaking memory in another module would be shown by a handle leak. I'm just stating that manipulating and interacting with other modules could indirectly cause leaks that you are still responsible for. If the library you are using specifies you must free or call some destroy function, you should follow the instructions!

So, we have ran the program and we noticed the memory has gone up. Let's see if we can find out where it is. First, we find the PID of the process and then we can use "CDB -P <PID>". You could also use WinDbg's GUI and select the process from a list. The first thing we do once we are broken into the program is "!heap" to display the heaps of the process.

0:000> !heap
NtGlobalFlag enables following debugging aids for new heaps:    tail checking
    disable coalescing of free blocks
Index   Address  Name      Debugging options enabled
  1:   00140000                 tail checking free checking validate parameters
  2:   00240000                 tail checking free checking validate parameters
  3:   00250000                 tail checking free checking validate parameters
  4:   00320000                 tail checking free checking validate parameters
0:000>

The next thing we will do is examine all the heaps and find out which ones are hording the most memory.

0:000> !heap 00140000
Index   Address  Name      Debugging options enabled
  1:   00140000
    Segment at 00140000 to 00240000 (00100000 bytes committed)
    Segment at 00510000 to 00610000 (00100000 bytes committed)
    Segment at 00610000 to 00810000 (00051000 bytes committed)
  2:   00240000
  3:   00250000
  4:   00320000
0:000> !heap 00240000
Index   Address  Name      Debugging options enabled
  1:   00140000
  2:   00240000
    Segment at 00240000 to 00250000 (00006000 bytes committed)
  3:   00250000
  4:   00320000
0:000> !heap 00250000
Index   Address  Name      Debugging options enabled
  1:   00140000
  2:   00240000
  3:   00250000
    Segment at 00250000 to 00260000 (00001000 bytes committed)
  4:   00320000
0:000> !heap 00320000
Index   Address  Name      Debugging options enabled
  1:   00140000
  2:   00240000
  3:   00250000
  4:   00320000
    Segment at 00320000 to 00330000 (00010000 bytes committed)
    Segment at 00410000 to 00510000 (000ee000 bytes committed)
0:000>

In bold, we have the segments that have the most memory committed. We will start with the first heap.

0:000> !heap 00140000 -a
Index   Address  Name      Debugging options enabled
  1:   00140000
    Segment at 00140000 to 00240000 (00100000 bytes committed)
    Segment at 00510000 to 00610000 (00100000 bytes committed)
    Segment at 00610000 to 00810000 (00051000 bytes committed)
    Flags:                50000062
    ForceFlags:           40000060
    Granularity:          8 bytes
    Segment Reserve:      00400000
    Segment Commit:       00002000
    DeCommit Block Thres: 00000200
    DeCommit Total Thres: 00002000
    Total Free Size:      00000226
    Max. Allocation Size: 7ffdefff
    Lock Variable at:     00140608
    Next TagIndex:        0000
    Maximum TagIndex:     0000
    Tag Entries:          00000000
    PsuedoTag Entries:    00000000
    Virtual Alloc List:   00140050
    UCR FreeList:        00140598
    FreeList Usage:      00040000 00400000 00000000 00000000
    FreeList[ 00 ] at 00140178: 00660118 . 00660118
    Unable to read nt!_HEAP_FREE_ENTRY structure at 00660118
    FreeList[ 12 ] at 00140208: 0023ff78 . 0023ff78
    Unable to read nt!_HEAP_FREE_ENTRY structure at 0023ff78
    FreeList[ 36 ] at 00140328: 0060fe58 . 0060fe58
    Unable to read nt!_HEAP_FREE_ENTRY structure at 0060fe58
    Segment00 at 00140640:
        Flags:           00000000
        Base:            00140000
        First Entry:     00140680
        Last Entry:      00240000
        Total Pages:     00000100
        Total UnCommit:  00000000
        Largest UnCommit:00000000
        UnCommitted Ranges: (0)

    Heap entries for Segment00 in Heap 00140000
        00140000: 00000 . 00640 [01] - busy (640)
        00140640: 00640 . 00040 [01] - busy (40)
        00140680: 00040 . 01818 [07] - busy (1800), 
            tail fill - unable to read heap entry extra at 00141e90
        00141e98: 01818 . 00040 [07] - busy (22), 
            tail fill - unable to read heap entry extra at 00141ed0
        00141ed8: 00040 . 00020 [07] - busy (5), 
            tail fill - unable to read heap entry extra at 00141ef0
        00141ef8: 00020 . 002f0 [07] - busy (2d8), 
            tail fill - unable to read heap entry extra at 001421e0
        001421e8: 002f0 . 00330 [07] - busy (314), 
            tail fill - unable to read heap entry extra at 00142510
        00142518: 00330 . 00330 [07] - busy (314), 
            tail fill - unable to read heap entry extra at 00142840
        00142848: 00330 . 00040 [07] - busy (24), 
            tail fill - unable to read heap entry extra at 00142880
        00142888: 00040 . 00040 [07] - busy (24), 
            tail fill - unable to read heap entry extra at 001428c0
        001428c8: 00040 . 00028 [07] - busy (10), 
            tail fill - unable to read heap entry extra at 001428e8
        001428f0: 00028 . 00058 [07] - busy (40), 
            tail fill - unable to read heap entry extra at 00142940
        00142948: 00058 . 00058 [07] - busy (40), 
            tail fill - unable to read heap entry extra at 00142998
        001429a0: 00058 . 00060 [07] - busy (44), 
            tail fill - unable to read heap entry extra at 001429f8
        00142a00: 00060 . 00020 [07] - busy (1), 
            tail fill - unable to read heap entry extra at 00142a18
        00142a20: 00020 . 00028 [07] - busy (10), 
            tail fill - unable to read heap entry extra at 00142a40
        00142a48: 00028 . 00050 [07] - busy (36), 
            tail fill - unable to read heap entry extra at 00142a90
        00142a98: 00050 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00142ca0
        00142ca8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00142eb0
        00142eb8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001430c0
        001430c8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001432d0
        001432d8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001434e0
        001434e8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001436f0
        001436f8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00143900
        00143908: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00143b10
        00143b18: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00143d20
        00143d28: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00143f30
        00143f38: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144140
        00144148: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144350
        00144358: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144560
        00144568: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144770
        00144778: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144980
        00144988: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144b90
        00144b98: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144da0
        00144da8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00144fb0
        00144fb8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001451c0
        001451c8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001453d0
        001453d8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001455e0
        001455e8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 001457f0
        001457f8: 00210 . 00210 [07] - busy (1f4), 
            tail fill - unable to read heap entry extra at 00145a00

In order to shorten the list, I hit "Control Break" a few times to get out of the listing. I notice that after a while, the memory blocks are the same size and they're larger sizes. The listing goes like this:

<ADDRESS>: <Current Size> . <PREVIOUS Size>

If I dump one of the addresses, this is what I see:

0:000> dd 001457f8
001457f8  00420042 001c0700 00006968 00000000
00145808  00000000 00000000 00000000 00000000
00145818  00000000 00000000 00000000 00000000
00145828  00000000 00000000 00000000 00000000
00145838  00000000 00000000 00000000 00000000
00145848  00000000 00000000 00000000 00000000
00145858  00000000 00000000 00000000 00000000
00145868  00000000 00000000 00000000 00000000

The first DWORD is the size, but it's split into two sections. The low word is the current size and the high word is the previous size. In order to get the true size, you must shift the value left by 3. All memory is being allocated in a granularity of 8. 42<<3 = 210 or 528. The second DWORD is the flags and the rest is the memory allocated to the program.

0:000> dc 001457f8
001457f8  00420042 001c0700 00006968 00000000  B.B.....hi......
00145808  00000000 00000000 00000000 00000000  ................
00145818  00000000 00000000 00000000 00000000  ................
00145828  00000000 00000000 00000000 00000000  ................
00145838  00000000 00000000 00000000 00000000  ................
00145848  00000000 00000000 00000000 00000000  ................
00145858  00000000 00000000 00000000 00000000  ................
00145868  00000000 00000000 00000000 00000000  ................
0:000>

If I do "DC" to dump the characters, I notice the allocated memory contains the word "hi". If I continue dumping the memory, I continue to see "hi" in the memory allocated at 528 bytes. This is now starting to become a pattern. Let's move onto the next heap.

0:000> !heap
NtGlobalFlag enables following debugging aids for new heaps:    tail checking
    disable coalescing of free blocks
Index   Address  Name      Debugging options enabled
  1:   00140000                 tail checking free checking validate parameters
  2:   00240000                 tail checking free checking validate parameters
  3:   00250000                 tail checking free checking validate parameters
  4:   00320000                 tail checking free checking validate parameters
0:000> !heap 00320000 -a
Index   Address  Name      Debugging options enabled
  1:   00140000
  2:   00240000
  3:   00250000
  4:   00320000
    Segment at 00320000 to 00330000 (00010000 bytes committed)
    Segment at 00410000 to 00510000 (000ee000 bytes committed)
    Flags:                50001062
    ForceFlags:           40000060
    Granularity:          8 bytes
    Segment Reserve:      00200000
    Segment Commit:       00002000
    DeCommit Block Thres: 00000200
    DeCommit Total Thres: 00002000
    Total Free Size:      000000b3
    Max. Allocation Size: 7ffdefff
    Lock Variable at:     00320608
    Next TagIndex:        0000
    Maximum TagIndex:     0000
    Tag Entries:          00000000
    PsuedoTag Entries:    00000000
    Virtual Alloc List:   00320050
    UCR FreeList:        00320598
    FreeList Usage:      00000800 00000000 00000000 00000000
    FreeList[ 00 ] at 00320178: 004fdac8 . 004fdac8
    Unable to read nt!_HEAP_FREE_ENTRY structure at 004fdac8
    FreeList[ 0b ] at 003201d0: 0032ffb0 . 0032ffb0
    Unable to read nt!_HEAP_FREE_ENTRY structure at 0032ffb0
    Segment00 at 00320640:
        Flags:           00000000
        Base:            00320000
        First Entry:     00320680
        Last Entry:      00330000
        Total Pages:     00000010
        Total UnCommit:  00000000
        Largest UnCommit:00000000
        UnCommitted Ranges: (0)

    Heap entries for Segment00 in Heap 00320000
        00320000: 00000 . 00640 [01] - busy (640)
        00320640: 00640 . 00040 [01] - busy (40)
        00320680: 00040 . 01818 [07] - busy (1800), 
            tail fill - unable to read heap entry extra at 00321e90
        00321e98: 01818 . 000a0 [07] - busy (88), 
            tail fill - unable to read heap entry extra at 00321f30
        00321f38: 000a0 . 00498 [07] - busy (480), 
            tail fill - unable to read heap entry extra at 003223c8
        003223d0: 00498 . 00098 [07] - busy (80), 
            tail fill - unable to read heap entry extra at 00322460
        00322468: 00098 . 00028 [07] - busy (d), 
            tail fill - unable to read heap entry extra at 00322488
        00322490: 00028 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00322568
        00322570: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00322648
        00322650: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00322728
        00322730: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00322808
        00322810: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003228e8
        003228f0: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003229c8
        003229d0: 000e0 . 000e8 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00322ab0
        00322ab8: 000e8 . 00238 [07] - busy (220), 
            tail fill - unable to read heap entry extra at 00322ce8
        00322cf0: 00238 . 000a8 [07] - busy (90), 
            tail fill - unable to read heap entry extra at 00322d90
        00322d98: 000a8 . 00058 [07] - busy (3e), 
            tail fill - unable to read heap entry extra at 00322de8
        00322df0: 00058 . 00060 [07] - busy (41), 
            tail fill - unable to read heap entry extra at 00322e48
        00322e50: 00060 . 00050 [07] - busy (31), 
            tail fill - unable to read heap entry extra at 00322e98
        00322ea0: 00050 . 00038 [07] - busy (1b), 
            tail fill - unable to read heap entry extra at 00322ed0
        00322ed8: 00038 . 00040 [07] - busy (26), 
            tail fill - unable to read heap entry extra at 00322f10
        00322f18: 00040 . 00030 [07] - busy (11), 
            tail fill - unable to read heap entry extra at 00322f40
        00322f48: 00030 . 00030 [07] - busy (17), 
            tail fill - unable to read heap entry extra at 00322f70
        00322f78: 00030 . 00028 [07] - busy (d), 
            tail fill - unable to read heap entry extra at 00322f98
        00322fa0: 00028 . 00048 [07] - busy (2f), 
            tail fill - unable to read heap entry extra at 00322fe0
        00322fe8: 00048 . 000d0 [07] - busy (b1), 
            tail fill - unable to read heap entry extra at 003230b0
        003230b8: 000d0 . 00080 [07] - busy (61), 
            tail fill - unable to read heap entry extra at 00323130
        00323138: 00080 . 00038 [07] - busy (1c), 
            tail fill - unable to read heap entry extra at 00323168
        00323170: 00038 . 00048 [07] - busy (2d), 
            tail fill - unable to read heap entry extra at 003231b0
        003231b8: 00048 . 00040 [07] - busy (22), 
            tail fill - unable to read heap entry extra at 003231f0
        003231f8: 00040 . 00030 [07] - busy (17), 
            tail fill - unable to read heap entry extra at 00323220
        00323228: 00030 . 00028 [07] - busy (e), 
            tail fill - unable to read heap entry extra at 00323248       
        00323250: 00028 . 00168 [07] - busy (149), 
            tail fill - unable to read heap entry extra at 003233b0
        003233b8: 00168 . 00058 [07] - busy (39), 
            tail fill - unable to read heap entry extra at 00323408
        00323410: 00058 . 00038 [07] - busy (1b), 
            tail fill - unable to read heap entry extra at 00323440
        00323448: 00038 . 00060 [07] - busy (43), 
            tail fill - unable to read heap entry extra at 003234a0
        003234a8: 00060 . 00030 [07] - busy (12), 
            tail fill - unable to read heap entry extra at 003234d0
        003234d8: 00030 . 00030 [07] - busy (18), 
            tail fill - unable to read heap entry extra at 00323500
        00323508: 00030 . 00038 [07] - busy (1e), 
            tail fill - unable to read heap entry extra at 00323538
        00323540: 00038 . 00028 [07] - busy (c), 
            tail fill - unable to read heap entry extra at 00323560
        00323568: 00028 . 00030 [07] - busy (14), 
            tail fill - unable to read heap entry extra at 00323590
        00323598: 00030 . 00028 [07] - busy (f), 
            tail fill - unable to read heap entry extra at 003235b8
        003235c0: 00028 . 00030 [07] - busy (18), 
            tail fill - unable to read heap entry extra at 003235e8
        003235f0: 00030 . 00040 [07] - busy (28), 
            tail fill - unable to read heap entry extra at 00323628
        00323630: 00040 . 00040 [07] - busy (27), 
            tail fill - unable to read heap entry extra at 00323668
        00323670: 00040 . 00038 [07] - busy (19), 
            tail fill - unable to read heap entry extra at 003236a0
        003236a8: 00038 . 00030 [07] - busy (17), 
            tail fill - unable to read heap entry extra at 003236d0
        003236d8: 00030 . 00050 [07] - busy (34), 
            tail fill - unable to read heap entry extra at 00323720
        00323728: 00050 . 00030 [07] - busy (11), 
            tail fill - unable to read heap entry extra at 00323750
        00323758: 00030 . 00030 [07] - busy (14), 
            tail fill - unable to read heap entry extra at 00323780
        00323788: 00030 . 00068 [07] - busy (4a), 
            tail fill - unable to read heap entry extra at 003237e8
        003237f0: 00068 . 00818 [07] - busy (800), 
            tail fill - unable to read heap entry extra at 00324000
        00324008: 00818 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003240e0
        003240e8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003241c0
        003241c8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003242a0
        003242a8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324380
        00324388: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324460
        00324468: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324540
        00324548: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324620
        00324628: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324700
        00324708: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003247e0
        003247e8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003248c0
        003248c8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003249a0
        003249a8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324a80
        00324a88: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324b60
        00324b68: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324c40
        00324c48: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324d20
        00324d28: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324e00
        00324e08: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324ee0
        00324ee8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00324fc0
        00324fc8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 003250a0
        003250a8: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00325180
        00325188: 000e0 . 000e0 [07] - busy (c8), 
            tail fill - unable to read heap entry extra at 00325260

In this heap, I see a lot of <address>: e0 . e0 allocations. This could be my leak. If I take a look at them, I notice they are the same:

0:000> dc 00325188
00325188  001c001c 00180700 66647361 61667361  ........asdfasfa
00325198  73666473 73666461 73666461 61666164  sdfsadfsadfsdafa
003251a8  61736673 73616664 61736664 73616664  sfsadfasdfsadfas
003251b8  66736166 73666473 66736661 66647361  fasfsdfsafsfasdf
003251c8  00617364 baadf00d baadf00d baadf00d  dsa.............
003251d8  baadf00d baadf00d baadf00d baadf00d  ................
003251e8  baadf00d baadf00d baadf00d baadf00d  ................
003251f8  baadf00d baadf00d baadf00d baadf00d  ................

Another thing you could do besides !heap is to simply take the starting address of the heap allocations and continuously "DC" on the heap looking for strings or other patterns.

01c << 3 = e0 or 224. So, let's look at the source code.

  char *p, *x;
  while(1)
  {
     p = malloc(200);
     strcpy(p, "asdfasfasdfsadfsadfsdafasfsadfasdfsadfasfasfsdfsafsfasdfdsa");

     x = LocalAlloc(LMEM_ZEROINIT, 500);
     strcpy(x, "hi");

     Sleep(1);
  }

Very simple, and obviously would cause a fast leak. Notice that we allocated "224" bytes instead of "200". This is because the allocation size includes the 2 DWORD headers as well as must be on an 8 byte boundary. If you take the memory that you were given in your program and subtract 8, you have the header information. If you know, take the allocated size, shift left by 3, then add this to that address, you will get the next allocated block of memory. Take our last allocation as an example:

0:000> dc 0325188 + e0
00325268  001c001c 00180700 66647361 61667361  ........asdfasfa
00325278  73666473 73666461 73666461 61666164  sdfsadfsadfsdafa
00325288  61736673 73616664 61736664 73616664  sfsadfasdfsadfas
00325298  66736166 73666473 66736661 66647361  fasfsdfsafsfasdf
003252a8  00617364 baadf00d baadf00d baadf00d  dsa.............
003252b8  baadf00d baadf00d baadf00d baadf00d  ................
003252c8  baadf00d baadf00d baadf00d baadf00d  ................
003252d8  baadf00d baadf00d baadf00d baadf00d  ................
0:000>

I will not go over the heap flags as I really never bother with them myself unless really necessary. The most important flag would basically be to tell you if the memory is currently allocated or not. This is told to you when you do !heap <heap> -a as "busy" generally means not free and "free" means the memory is free. The "00180700" when allocated generally turns into "00180400" when free. You can experiment by writing a program to allocate and free memory while watching the flags. Just be careful that there's no other thread or the memory was allocated in the time after you freed it but before you checked its flags!

Private Heaps Verse Global Heaps

I have introduced you to the !heap function, but there's something I should mention to you. It does not show the global heap. If you have created a global variable, it will not show up in any of the heaps listed in !heap and will not follow the same rules. The global heap cannot be destroyed while these private heaps can.

The global heap you will have problems with corruption though, not leaking; which will be the next topic for discussion. I will not go over the global heap in this tutorial though.

Keeping Track Of Allocations

There is another method that you could use in your program to track down memory leaks. This would be the creation of your own memory allocation routines. These routines would simply behave as the following:

PVOID MyAllocationRoutine(DWORD dwSize)
{
 PVOID pMem = malloc(dwSize + sizeof(_DEBUG_STRUCTURE));

 if(pMem)
 {
    _DEBUG_STRUCTURE *pDebugStruc = (_DEBUG_STRUCTURE *)pMem;

    /* Fill In Your Debug Information Here */

    /* Make Sure You Give the Application the memory AFTER your debug structure */
    pMem = pDebugStruc + 1; 
  }

 return pMem;
}

You simply append your own header to all allocations. This information could be as complex as returning the return address of who allocated the memory, thread identifier, etc. This is how programs like boundschecker work. They can simply replace your allocations and find out who's allocating the memory and keep track of it. Your program could do this itself. You could even create a global variable and add all the memory into a linked list and create a debug helper extension to walk through it dumping all the memory and information. Creating a debug helper extension would be in a later tutorial.

As you can see, you can simply create your own memory allocation thunks to add useful information to the allocated block to help you keep track of memory and find leaks or even memory corruption. You could even #define this function to only be your code when in a certain mode, such as debug. The function you used could be redefined to LocalAlloc or malloc on a normal build. You could even always use it and enable it with a special registry key or flag. There're a lot of possibilities.

Also, remember that you now need to create a "free" routine. This routine should simply subtract the size of your structure before calling the free API.

Heap Corruption

Also known as "Memory Corruption", is basically when variables start overwriting their boundaries. The most common cause of simple heap corruption is caused when you use the wrong method to free the memory. For example, if you allocate with malloc() but free with LocalFree(). They use different heaps and could even use different underlying methods to allocate or keep track of memory all together. The problem is that these functions may still try to free the memory using their algorithms without doing validation on the heap being freed. This is when the corruption occurs. Look at the above section on creating your own malloc() thunk function. Other APIs could be performing their own thunks so it's always best to use the matching APIs to free the memory!

The other common causes are simply over running your boundary, such as writing to more memory than you've allocated and even simply just randomly writing to memory that the program does not own. Heap corruption can be harder to track down than memory leaks. The strategy of tracking allocations does not help, especially if you want to compile that in after the fact. This is because now you change the allocation sizes, so it's possible you will not corrupt the heap the same or perhaps there's enough padding it won't become corrupted.

I have written a program with a few heap problems. Now, heap problems do not surface immediately, they usually surface over time. They can surface when another part of the program goes to use the corrupted memory and trap, or when you free or allocate another variable.

I ran this program and got a few dialog boxes about invalid memory referencing. So, I run it in the debugger to see what happens.

C:/programs/DirectX/Games/src/Games/temp/bin>;cdb temp 

Microsoft (R) Windows Debugger  Version 6.3.0005.1
Copyright (c) Microsoft Corporation. All rights reserved. 

CommandLine: temp
Symbol search path is: SRV*c:/symbols*http://msdl.microsoft.com/download/symbols 

Executable search path is:
ModLoad: 00400000 00404000   temp.exe
ModLoad: 77f50000 77ff7000   ntdll.dll
ModLoad: 77e60000 77f46000   C:/WINDOWS.0/system32/kernel32.dll
ModLoad: 77c10000 77c63000   C:/WINDOWS.0/system32/MSVCRT.dll
(a20.710): Break instruction exception - code 80000003 (first chance)
eax=00241eb4 ebx=7ffdf000 ecx=00000004 edx=77f51310 esi=00241eb4 edi=00241f48
eip=77f75a58 esp=0012fb38 ebp=0012fc2c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
77f75a58 cc               int     3
0:000>; g
(a20.710): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=61736664 ebx=00000004 ecx=73616664 edx=00142ab8 esi=00142ab8 edi=00140000
eip=77f8452d esp=0012f7e4 ebp=0012f9fc iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00010246
ntdll!RtlAllocateHeapSlowly+0x6bd:
77f8452d 8901             mov     [ecx],eax         ds:0023:73616664=????????
0:000>;

We receive a first chance exception. This generally means if I hit "g", the program will continue to run. If I get a "second chance" exception, the program is then trapped. However, this still does not look good. It's a good sign of memory corruption. Let's take a look. The first thing we want to do is remember what we were taught in the first tutorials. We need to find out why this memory is being referenced, who's referencing it and where it came from. The first way to do this? Our old friend, the stack trace!

0:000> kb
ChildEBP RetAddr  Args to Child
0012f9fc 77f9d959 00140000 50140169 00000006 ntdll!RtlAllocateHeapSlowly+0x6bd
0012fa80 77f83eb1 00140000 50140169 00000006 ntdll!RtlDebugAllocateHeap+0xaf
0012fcac 77f589f2 00140000 40140068 00000006 ntdll!RtlAllocateHeapSlowly+0x41
0012fee4 77e7a6d4 00140000 40140068 00000006 ntdll!RtlAllocateHeap+0xe44
0012ff30 00401024 00000040 00000006 00000000 kernel32!LocalAlloc+0x58
0012ff4c 0040113b 00000001 00322470 00322cf8 temp!main+0x24
0012ffc0 77e814c7 00000000 00000000 7ffdf000 temp!mainCRTStartup+0xe3
0012fff0 00000000 00401058 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000>

So, we are allocating memory and basically NTDLL is trapping! This is a good sign of memory corruption. Let's look further, what is the memory attempted to be referenced.

0:000>; u eip - 20
ntdll!RtlAllocateHeapSlowly+0x69d:
77f8450d 058845b356       add     eax,0x56b34588
77f84512 8b7de4           mov     edi,[ebp-0x1c]
77f84515 57               push    edi
77f84516 e85eeaffff       call    ntdll!RtlpUpdateIndexRemoveBlock (77f82f79)
77f8451b 8b4608           mov     eax,[esi+0x8]
77f8451e 89855cffffff     mov     [ebp-0xa4],eax
77f84524 8b4e0c           mov     ecx,[esi+0xc]
77f84527 898d58ffffff     mov     [ebp-0xa8],ecx
0:000>; u
ntdll!RtlAllocateHeapSlowly+0x6bd:
77f8452d 8901             mov     [ecx],eax

From the looks of this, it appears to be some type of linked list or similar data structure. I have put in bold the assembly of interest. We first see that ECX is being de-referenced as a pointer. [ecx], as stated in another tutorial, is the same as DWORD *pECX; *pECX = EAX; in C. So, we need to find where the ECX value came from. We see "ECX, [ESI + 0Ch]", which would basically be:

DWORD *pECX, *pESI; pECX = pESI[12/4]; 

Remember, in assembly there's no types so the arrays are always indexed as byte sizes rather than data type sizes. (I assume if you're reading this you know about pointers!) So, let's dump [ESI + C] and see what's there.

0:000> dc esi + c
00142ac4  73616664 66736166 73666473 66736661  dfasfasfsdfsafsf
00142ad4  66647361 00617364 feeefeee feeefeee  asdfdsa.........
00142ae4  feeefeee feeefeee feeefeee feeefeee  ................
00142af4  feeefeee feeefeee feeefeee feeefeee  ................
00142b04  feeefeee feeefeee feeefeee feeefeee  ................
00142b14  feeefeee feeefeee feeefeee feeefeee  ................
00142b24  feeefeee feeefeee feeefeee feeefeee  ................
00142b34  feeefeee feeefeee feeefeee feeefeee  ................

The trap:

77f8452d 8901  mov  [ecx],eax  ds:0023:73616664=????????

This is very simple now! This is a string, so we just need to look through our program to find out where we allocated this string.

x = LocalAlloc(LMEM_ZEROINIT, 5);
strcpy(x, "asdfasfasdfsadfsadfsdafasfsadfasdfsadfasfasfsdfsafsfasdfdsa");
p = LocalAlloc(LMEM_ZEROINIT, 6);
strcpy(p, "hi");
LocalFree(x);
free(p);

As we can see, we have overwritten a lot of memory beyond our "5" bytes we wanted to allocate. This was a very simple example. Sometimes, it could just be one byte overwritten and you may need to search backwards through memory to find out if it belongs to a string or the "null" of a string. A very common mistake is to forget to leave 1 character for the NULL. A lot of times, this is OK as we see we allocate on granularities of 8, so it's unnoticed. Other times though, it's a nightmare to track down because once the memory has been overwritten, the culprit is long gone once its effect actually takes hold!

Another good thing to do is step through the program. If the place being overwritten does not change, it's simple. Step through and keep checking if it's been overwritten. You can walk functions to narrow down the location. This is also assuming a simple problem, not a complex scenario. You may also want to check other memory locations and etc. to track down.

Another good tool is the "breakpoint". "ba r1 xxxx" means "break if anyone attempts to read or write to this address". This is very useful as well if the address is constant. It will break as soon as someone attempts to write to it. There's other methods you could attempt as well, such as narrowing down the functionality that causes the problems, enabling "global flags", etc.

I know that I mentioned in the above first paragraph that the code used to check for memory leaks could not be used to check for heap corruption. This is true and false. I basically meant, in its current state, you could not expect to use it to help find memory corruption. With a little tweak, you can pad the end of the allocation and the beginning with values that you would not expect the program to use. Then, at the end, when you free the data, check the signature to validate it was unchanged. If it did change, it's a possible memory overwrite. This assumes your corruption is continuous or will hit the boundary and not skip it. This also assumes the problem lies in the allocated buffer and not somewhere else in your code!

Other Tools

There are other tools that can help you spot and track down memory corruption and leaks.

Performance Monitor

This tool comes with Windows, "perfmon". This allows you to monitor and record system performance using the options you choose. This can help you study a process' memory footprint over time. This is a better alternative than "Task Manger", for spotting slow leaks.

Bounds Checker

This is a tool which I described above. It will help you to track down memory that is not freed at the close of your application. It will also help to find other types of leaks and even help find memory corruption. This is a very easy tool to use and will display the locations in your code which caused the allocation that was never freed. It will even tell you how much memory was not freed.

Global Flags

There are options in the registry to enable heap validation and heap checking. There is a utility that comes with the debug tools called "gflags" that can help set these flags in the registry for you. I may go over these in a future tutorial, however the debug tools do come with documentation.

QuickView: System Explorer

This is a utility that I have written and it is a general Windows 2000 and higher debug helper. It does not do real time data at this time and is just for exploring different aspects of the system to help you track down problems. If you want to use it, there is an .RTF file installed with it that helps explain all the features.

Conclusion

This tutorial should hopefully have introduced you to memory leaks and heap corruption from usermode. This tutorial should also have helped educate you in how to approach such problems with your own applications.

About Toby Opferman

国央企创新负责人如何通过平台提升技术需求挖掘与资源配置的精准性?.docx 国央企创新负责人如何通过平台提升技术需求挖掘与资源配置的精准性? 立即下载

相关推荐

科技中介服务机构在协助企业对接科研成果时,如何提升服务响应速度与专业化水平.docx

科技中介服务机构在协助企业对接科研成果时,如何提升服务响应速度与专业化水平

使用WINDBG检测内存泄漏

使用WINDBG检测内存泄漏 一、使用命令!heap –s抓取快照   二、再次使用命令抓取快照 三、使用命令查看内存情况!heap –stat –h addr【!heap -stat -h 03570000】 四、使用命令分别查看较高的内存使用情况!heap –flt s 16c 五、使用命令查看调用堆栈即可以判断内存泄漏情况!heap –p –a 0bba8530

flyingleo1981的专栏 3179

国央企创新负责人在推动内部技术成果转化与外部协同创新时,如何实现资源的高效整合与协同联动?.docx

国央企创新负责人在推动内部技术成果转化与外部协同创新时,如何实现资源的高效整合与协同联动?

数据结构之堆(Heap)(9)

1 堆 1)堆中某个节点的值总是不大于或不小于其父节点的值; 2)堆总是一棵完全数。    将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。下面主要讨论二叉堆。    堆常用数组实现,如果父亲节点在数组中的位置是index,则其左节点位置是2*index+1,右节点位置是2*index+2.    堆为什么通常用数组实现? To insert an ele

公众号(猴子数据分析) 1705

机器人入门实战:用 ROS2 和 Gazebo 先把差速车 URDF 跑起来,再做一版 SLAM 建图(含教学手册与源码骨架)

原创教学资源包(VIP专属)。围绕机器人开发项目实战搭建可复现闭环:用差速车 URDF 把 base_link、左右轮和激光连杆的关节树写自洽,配一间教学库房的 Gazebo 世界与 launch 骨架;再提供一版合成二维占用栅格建图,对比纯里程计写入和扫描匹配修正,用来分辨 odom 系漂移与 map 系 SLAM 输出。内含中文实战手册与可 make 抽检的 ROS 2 源码骨架。实现均为教学桩,注释标明不是完整 Navigation2 栈,不能替代实机传感器标定,也不是 Cartographer / slam_toolbox 的生产方案。 使用方法:解压后进入 src/ 目录(需 Linux、Python 3;不必安装 ROS 2 或 Gazebo 也能抽检)。先执行 make clean && make test,或运行 python3 scripts/smoke_test.py,看到 ALL SMOKE TESTS PASSED 后再阅读手册.md、README.md 与源码注释。若本机已安装 ROS 2 Humble/Jazzy 与 Gazebo,再把 src/diffbot_slam_tutor 放入工作空间,按 README 中「读者本地环境再跑」的 launch 命令启动仿真。细节见手册.md 与 src/README_SRC.txt。 适合作为成长任务「机器人开发项目实战」的自制合规 VIP 资源上传。建议售价 9.90 元。模型与数据均为虚构示例,不含刷量、搬运或真实个人信息。

两路半桥LLC谐振变换器软开关Simulink仿真(模拟谐振参数不同、分裂电容)

内容概要:本文介绍了“两路半桥LLC谐振变换器软开关Simulink仿真”资源包,重点在于基于Simulink平台对两路半桥LLC谐振变换器进行高精度建模与动态仿真,特别考虑了谐振参数不一致及分裂电容等非理想工况对系统性能的影响。该仿真模型不仅涵盖了主电路拓扑、软开关实现机制与谐振网络设计,还深入分析了不同参数配置下系统的启动特性、稳态效率、动态响应及软开关条件的满足情况。通过参数扫描与对比仿真,帮助用户掌握LLC变换器的关键设计要素与优化方法,具备较强的工程指导价值和科研教学意义。; 适合人群:电力电子、电气工程及其自动化等相关专业的高校师生,以及从事高频开关电源、新能源变换器、车载充电机等领域的研发工程师和技术人员。; 使用场景及目标:① 掌握两路并联半桥LLC谐振变换器的建模与仿真方法;② 理解软开关技术在提升变换器效率与降低EMI方面的关键作用;③ 分析谐振参数偏差与分裂电容结构对系统均流特性、增益曲线及软开关范围的影响;④ 支持科研项目中的拓扑验证、控制策略开发与故障工况分析; 阅读建议:建议结合Simulink仿真模型与配套说明文档进行学习,重点关注谐振腔参数设置、驱动时序设计及软开关波形观测;可通过调整谐振电感、电容值或引入参数失配,观察系统行为变化,深入理解LLC变换器的稳定边界与优化设计原则。

数据结构与算法分析学习笔记(一)

数据结构与算法分析 chapter 1 computer design goals: todesign an algorithm that is easy to understand,code and debug to design an algorthim that makes efficient use of the computer’s resources 1.1 A philosophy of data structures 1.1.1 the need of data structure da

weixin_43469023的博客 4888

双向全桥CLLC谐振变换器仿真模型(Simulink仿真实现)

内容概要:本文系统介绍了双向全桥CLLC谐振变换器的Simulink仿真模型,重点阐述了变频控制、闭环仿真及其与多种电力电子拓扑(如维也纳整流器、DAB变换器、图腾柱PFC等)的联合仿真设计。资源包涵盖2.5kW双向全桥LLC、V2G电动汽车充放电系统、三相PFC、多相交错LLC等多种变换器的仿真案例,并深入展示了三电平VIENNA整流器在双闭环PI控制与SVPWM调制策略下,结合上下电容电压差值补偿实现中点电位平衡的先进控制方法。通过仿真验证,该策略不仅有效维持了800V直流母线电压的稳定,还在动态工况下显著提升了电容电压的均衡度,同时保持了较低的输入电流THD,充分体现了SVPWM与中点平衡控制协同优化的技术优势。; 适合人群:具备电力电子与自动控制理论基础,从事新能源发电、电动汽车、工业电源、微电网等领域的研究生、工程师及科研人员。; 使用场景及目标:① 学习并构建高频谐振变换器(如LLC、CLLC)的Simulink仿真模型,掌握其变频与混合控制策略;② 深入理解并应用中点电位平衡、双闭环PI控制、SVPWM调制等核心技术于VIENNA等三电平变换器中;③ 复现高水平期刊论文中的控制算法,服务于科研项目申报、学术论文撰写或工程产品开发。; 阅读建议:该资源为综合性仿真资料库,建议使用者结合自身研究方向(如电动汽车充电、新能源并网)选择对应案例进行精学,重点关注控制策略的设计逻辑、仿真参数的整定方法以及SVPWM与中点平衡的协同机制,并充分利用附带的计算书和参考文献深化理解,以实现从仿真模型到实际工程应用的顺利转化。

Debug Tutorial Part 6: Navigating The Kernel Debugger

IntroductionIn this tutorial, we will be covering a few of the basic features of the kernel debugger, and get used to using it. I obviously cant cover everything, so only select topics will be cove

2550

全桥LLC谐振(变频+移相混合控制)变换器闭环仿真模型(Simulink仿真实现)

全桥LLC谐振(变频+移相混合控制)变换器闭环仿真模型(Simulink仿真实现)

虚拟同步发电机(VSG)惯量阻尼自适应控制仿真模型(simulink仿真实现)

内容概要:本文提供了“虚拟同步发电机(VSG)惯量阻尼自适应控制仿真模型”的Simulink仿真实现方法,系统阐述了如何通过仿真手段研究VSG在电力系统中的动态响应特性,重点聚焦于其惯量与阻尼的自适应控制策略。文中详细说明了模型构建流程,涵盖控制算法设计、参数调节、根轨迹分析等内容,并通过对比不同调节系数下的系统响应,深入探讨了控制参数对系统稳定性与动态性能的影响。该仿真模型有效复现了VSG模拟传统同步发电机惯性与阻尼特性的能力,对于提升新能源并网系统的频率稳定性和抗扰动能力具有重要研究价值。; 适合人群:具备电力系统、自动化或相关领域基础知识,从事新能源并网、微电网控制、电力电子与电力系统仿真研究的研究生、科研人员及工程师。; 使用场景及目标:①用于教学与科研中深入理解虚拟同步发电机的工作原理及其在现代电力系统中的应用;②为新能源发电系统接入电网时的稳定性分析与控制策略设计提供仿真验证平台;③支持对VSG控制参数进行优化,提升系统应对负荷波动和扰动的能力。; 阅读建议:建议读者结合Matlab/Simulink软件动手实践,按照文档结构逐步搭建仿真模型,重点关注控制环路设计与参数整定部分,同时参考提供的代码与仿真结果进行对比分析,以加深对VSG自适应控制机制的理解。

Debug Tutorial

Debug Tutorial Part 1: Beginning Debugging Using CDB and NTSD Debug Tutorial Part 2: The StackDebug Tutorial Part 3: The Heap Debug Tutorial Part 4: Writing WINDBG Extensions Debug Tutorial Part 5: Ha

tianyu的专栏 - Linux site:blog.youkuaiyun.com/wishfly 1014

两相交错并联Boost电路仿真模型(Simulink仿真实现)

内容概要:本文介绍了“两相交错并联Boost电路仿真模型(Simulink仿真实现)”的技术资源,属于电力电子变换器领域的仿真研究内容。该模型基于Simulink平台构建,旨在深入分析两相交错并联Boost电路的工作原理、动态响应特性及控制策略。交错并联结构通过错开各相开关管的导通时刻,有效降低输入电流纹波、提升系统效率与功率密度,适用于中高功率DC-DC变换应用场景。资源包含完整的主电路建模、PWM驱动信号生成、闭环控制器设计(如电压/电流双闭环控制)等关键模块,并可能涵盖软启动、均流控制等关键技术问题。整个仿真模型强调理论分析与工程实践的紧密结合,不仅有助于理解电路基本行为,也为优化控制算法提供验证平台,服务于科研与教学需求。; 适合人群:电气工程、自动化、电力电子及相关专业的高校本科生、研究生,以及从事电源系统研发的工程技术人员,具备一定的电路理论、自动控制原理和Simulink仿真基础者更佳。; 使用场景及目标:① 学习和掌握两相交错并联Boost电路的拓扑结构、工作机理及性能优势;② 利用Simulink进行电力电子电路的建模与仿真,深入理解PWM控制、闭环调节、均流控制等核心技术的应用;③ 为科研项目、课程设计或毕业设计提供可复用的仿真案例和技术支持,辅助开展中高功率电源系统的创新设计与优化研究。; 阅读建议:建议结合Simulink软件动手搭建并调试模型,通过调整占空比、开关频率、电感参数等变量,观察其对输入电流纹波、输出电压稳定性及动态响应的影响,以深化对电路特性的理解;同时可参考文中列出的其他相关资源(如LLC、PFC等仿真模型)进行横向对比学习,拓宽电力电子系统设计视野。

WinDbg调试推荐材料

Debug Tutorial Part 1: Beginning Debugging Using CDB and NTSDLearn how to debug problems in software.Debug Tutorial Part 2: The StackIntroduction to the most important ally in the fight against bugs, ...

iteye_10234的博客 177

区域电网含风光火压缩空气储能抽水蓄能以及电化学储能的联合调度,加入IEEE39系统,进行潮流计算,分析电能质量研究(Matlab代码实现)

内容概要:本文研究了包含风能、太阳能、火电以及压缩空气储能、抽水蓄能和电化学储能等多种能源形式的区域电网联合调度问题,通过将系统接入IEEE 39节点标准测试系统,利用Matlab平台开展潮流计算与电能质量分析,旨在提升高比例可再生能源接入背景下电网运行的稳定性与供电品质。研究重点涵盖多类型电源与多种储能系统的协调优化调度策略设计,深入探讨储能系统在削峰填谷、平滑新能源出力波动方面的关键作用,并对复杂运行工况下电压偏差、频率波动、谐波污染等电能质量问题进行评估与改善。通过构建完整的仿真模型,系统性地分析了联合调度对电网电能质量的影响机制,为现代电力系统中多能互补与协同控制提供了理论依据和技术参考。; 适合人群:具备电力系统分析、优化调度及电能质量相关基础知识,熟悉Matlab/Simulink仿真环境的研究生、科研人员及电力系统相关领域的工程师。; 使用场景及目标:①为含多种储能的区域电网联合调度研究提供完整的Matlab仿真案例与开源代码参考;②分析大规模新能源接入对IEEE 39系统潮流分布及电能质量的综合影响,并定量评估不同类型储能系统的调控效果;③为电力系统优化调度、电能质量综合治理、多能协同控制等学术研究与工程项目提供可复现的技术支撑与解决方案。; 阅读建议:本资源以Matlab代码实现为核心,读者应在掌握电力系统基本理论的基础上,重点关注联合调度的数学建模过程、储能系统充放电策略的制定逻辑以及电能质量各项评价指标的计算方法。建议在学习过程中动手调试仿真代码,尝试调整系统参数、优化目标权重或改变新能源渗透率,以深入理解系统动态行为与控制策略的有效性。

MATLAB2021b详细安装教程

1.下载 2.安装 下载安装包后打开iso文件,若是无法打开请用解压软件打开。 ①打开MATLAB_R2021b_win64,双击软件开始安装 ② 一直点击下一步,直到步骤③ ③选择我已有我的许可证的文件密钥,并输入62551-02011-26857-57509-64399-54230-13279-37181-62117-65158-40352-64197-45508-24369-45954-39446-39538-16936-10698-58393-44718-32560-10501-40

惜洛-Jankin的博客 9万+

C/C++堆栈指引

C/C++堆栈指引 Binhua Liu 前言     我们经常会讨论这样的问题:什么时候数据存储在堆栈(Stack)中,什么时候数据存储在堆(Heap)中。我们知道,局部变量是存储在堆栈中的;debug时,查看堆栈可以知道函数的调用顺序;函数调用时传递参数,事实上是把参数压入堆栈,听起来,堆栈象一个大杂烩。那么,堆栈(Stack)到底是如何工作的呢? 本文将详解C/C++堆栈的工作机制。阅

binhualiu1983的博客 1175

从零组装电赛送药小车:OpenMV视觉核心+STM32控制,我的软硬件联调全记录

本文详细记录了从零构建电赛智能送药小车的全过程,重点解析了OpenMV视觉识别与STM32控制的深度协同方案。通过硬件选型对比、通信协议设计、PID调参等实战经验,解决了巡线精度、十字路口识别等核心问题,最终实现稳定可靠的送药小车系统,助力电子设计竞赛取得优异成绩。

weixin_42641741的博客 449

libFuzzer实战指南:从零构建高效模糊测试,提升C/C++代码安全

模糊测试(Fuzzing)是一种通过向程序提供大量随机、非预期输入数据,以自动化发现崩溃、内存泄漏等安全漏洞的软件测试技术。其核心原理在于利用覆盖引导(Coverage-guided)机制,通过编译时插桩监控代码执行路径,并基于反馈进化式地生成测试用例,从而高效探索程序的深层逻辑和异常分支。这项技术的核心价值在于能够发现传统人工测试难以触及的隐蔽缺陷,如缓冲区溢出、整数溢出等,极大地提升了软件的安全性与健壮性。在工程实践中,模糊测试广泛应用于安全研究、质量保障及开源项目持续集成等场景。本文聚焦于Google

weixin_34414650的博客 491

西门子G120变频器在立体仓库堆垛机应用参数设置

本文详细介绍了G120 CU240E及CU250S变频器的参数设置流程。主要内容包括:1)快速调试步骤,从新建项目、扫描节点到设置电机类型、负载类型等参数;2)抱闸参数设置方法;3)参数下载异常时的处理方案。重点说明了通过STARTER软件进行向导式调试的完整过程,包括电机铭牌参数输入、识别方式选择等关键环节,并提示了通讯连接注意事项。当下载失败时,提供了通过导入导出项目文件解决问题的具体方法。

xdpcxq的专栏 864

2024年上半年试题二:论性能测试方法及其应用

2025年1月,我以系统分析师的身份参与了公司OA系统的开发与建设。该系统包含的功能有打卡签到、公文流转、工资查询等。我们吸取了之前由于系统使用人数激增暴露出的短时间卡顿等问题,所以在本次开发中,我们非常重视系统的性能测试。通过自动化的测试工具模拟多种正常、峰值及异常负载条件对系统的各项性能指标进行测试。性能测试在软件的质量保证中起着重要的作用,它包括的测试内容丰富多样,常见的负载测试和压力测试都属于性能测试。

银帅 549
上一篇: Windows Debuggers: Part 1: A WinDbg Tutorial
下一篇: Debug Tutorial Part 1: Beginning Debugging Using CDB and NTSD
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值