Assorted spelling fixes.
[wine] / dlls / kernel32 / toolhelp.c
1 /*
2  * Misc Toolhelp functions
3  *
4  * Copyright 1996 Marcus Meissner
5  * Copyright 2005 Eric Pouech
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "config.h"
23
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #ifdef HAVE_UNISTD_H
28 # include <unistd.h>
29 #endif
30 #include <ctype.h>
31 #include <assert.h>
32 #include "ntstatus.h"
33 #define WIN32_NO_STATUS
34 #include "windef.h"
35 #include "winbase.h"
36 #include "winerror.h"
37 #include "tlhelp32.h"
38 #include "winnls.h"
39 #include "winternl.h"
40
41 #include "wine/debug.h"
42
43 WINE_DEFAULT_DEBUG_CHANNEL(toolhelp);
44
45 struct snapshot
46 {
47     int         process_count;
48     int         process_pos;
49     int         process_offset;
50     int         thread_count;
51     int         thread_pos;
52     int         thread_offset;
53     int         module_count;
54     int         module_pos;
55     int         module_offset;
56     char        data[1];
57 };
58
59 static WCHAR *fetch_string( HANDLE hProcess, UNICODE_STRING* us)
60 {
61     WCHAR*      local;
62
63     local = HeapAlloc( GetProcessHeap(), 0, us->Length );
64     if (local)
65     {
66         if (!ReadProcessMemory( hProcess, us->Buffer, local, us->Length, NULL))
67         {
68             HeapFree( GetProcessHeap(), 0, local );
69             local = NULL;
70         }
71     }
72     us->Buffer = local;
73     return local;
74 }
75
76 static BOOL fetch_module( DWORD process, DWORD flags, LDR_MODULE** ldr_mod, ULONG* num )
77 {
78     HANDLE                      hProcess;
79     PROCESS_BASIC_INFORMATION   pbi;
80     PPEB_LDR_DATA               pLdrData;
81     NTSTATUS                    status;
82     PLIST_ENTRY                 head, curr;
83     BOOL                        ret = FALSE;
84
85     *num = 0;
86
87     if (!(flags & TH32CS_SNAPMODULE)) return TRUE;
88
89     if (process)
90     {
91         hProcess = OpenProcess( PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, process );
92         if (!hProcess) return FALSE;
93     }
94     else
95         hProcess = GetCurrentProcess();
96
97     status = NtQueryInformationProcess( hProcess, ProcessBasicInformation,
98                                         &pbi, sizeof(pbi), NULL );
99     if (!status)
100     {
101         if (ReadProcessMemory( hProcess, &pbi.PebBaseAddress->LdrData,
102                                &pLdrData, sizeof(pLdrData), NULL ) &&
103             ReadProcessMemory( hProcess,
104                                &pLdrData->InLoadOrderModuleList.Flink,
105                                &curr, sizeof(curr), NULL ))
106         {
107             head = &pLdrData->InLoadOrderModuleList;
108
109             while (curr != head)
110             {
111                 if (!*num)
112                     *ldr_mod = HeapAlloc( GetProcessHeap(), 0, sizeof(LDR_MODULE) );
113                 else
114                     *ldr_mod = HeapReAlloc( GetProcessHeap(), 0, *ldr_mod,
115                                             (*num + 1) * sizeof(LDR_MODULE) );
116                 if (!*ldr_mod) break;
117                 if (!ReadProcessMemory( hProcess,
118                                         CONTAINING_RECORD(curr, LDR_MODULE,
119                                                           InLoadOrderModuleList),
120                                         &(*ldr_mod)[*num],
121                                         sizeof(LDR_MODULE), NULL))
122                     break;
123                 curr = (*ldr_mod)[*num].InLoadOrderModuleList.Flink;
124                 /* if we cannot fetch the strings, then just ignore this LDR_MODULE
125                  * and continue loading the other ones in the list
126                  */
127                 if (!fetch_string( hProcess, &(*ldr_mod)[*num].BaseDllName )) continue;
128                 if (fetch_string( hProcess, &(*ldr_mod)[*num].FullDllName ))
129                     (*num)++;
130                 else
131                     HeapFree( GetProcessHeap(), 0, (*ldr_mod)[*num].BaseDllName.Buffer );
132             }
133             ret = TRUE;
134         }
135     }
136     else SetLastError( RtlNtStatusToDosError( status ) );
137
138     if (process) CloseHandle( hProcess );
139     return ret;
140 }
141
142 static void fill_module( struct snapshot* snap, ULONG* offset, ULONG process,
143                          LDR_MODULE* ldr_mod, ULONG num )
144 {
145     MODULEENTRY32W*     mod;
146     ULONG               i;
147     SIZE_T              l;
148
149     snap->module_count = num;
150     snap->module_pos = 0;
151     if (!num) return;
152     snap->module_offset = *offset;
153
154     mod = (MODULEENTRY32W*)&snap->data[*offset];
155
156     for (i = 0; i < num; i++)
157     {
158         mod->dwSize = sizeof(MODULEENTRY32W);
159         mod->th32ModuleID = 1; /* toolhelp internal id, never used */
160         mod->th32ProcessID = process ? process : GetCurrentProcessId();
161         mod->GlblcntUsage = 0xFFFF; /* FIXME */
162         mod->ProccntUsage = 0xFFFF; /* FIXME */
163         mod->modBaseAddr = ldr_mod[i].BaseAddress;
164         mod->modBaseSize = ldr_mod[i].SizeOfImage;
165         mod->hModule = ldr_mod[i].BaseAddress;
166
167         l = min(ldr_mod[i].BaseDllName.Length, sizeof(mod->szModule) - sizeof(WCHAR));
168         memcpy(mod->szModule, ldr_mod[i].BaseDllName.Buffer, l);
169         mod->szModule[l / sizeof(WCHAR)] = '\0';
170         l = min(ldr_mod[i].FullDllName.Length, sizeof(mod->szExePath) - sizeof(WCHAR));
171         memcpy(mod->szExePath, ldr_mod[i].FullDllName.Buffer, l);
172         mod->szExePath[l / sizeof(WCHAR)] = '\0';
173
174         mod++;
175     }
176
177     *offset += num * sizeof(MODULEENTRY32W);
178 }
179
180 static BOOL fetch_process_thread( DWORD flags, SYSTEM_PROCESS_INFORMATION** pspi,
181                                   ULONG* num_pcs, ULONG* num_thd)
182 {
183     NTSTATUS                    status;
184     ULONG                       size, offset;
185     PSYSTEM_PROCESS_INFORMATION spi;
186
187     *num_pcs = *num_thd = 0;
188     if (!(flags & (TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD))) return TRUE;
189
190     *pspi = HeapAlloc( GetProcessHeap(), 0, size = 4096 );
191     for (;;)
192     {
193         status = NtQuerySystemInformation( SystemProcessInformation, *pspi,
194                                            size, NULL );
195         switch (status)
196         {
197         case STATUS_SUCCESS:
198             *num_pcs = *num_thd = offset = 0;
199             spi = *pspi;
200             do
201             {
202                 spi = (SYSTEM_PROCESS_INFORMATION*)((char*)spi + offset);
203                 if (flags & TH32CS_SNAPPROCESS) (*num_pcs)++;
204                 if (flags & TH32CS_SNAPTHREAD) *num_thd += spi->dwThreadCount;
205             } while ((offset = spi->NextEntryOffset));
206             return TRUE;
207         case STATUS_INFO_LENGTH_MISMATCH:
208             *pspi = HeapReAlloc( GetProcessHeap(), 0, *pspi, size *= 2 );
209             break;
210         default:
211             SetLastError( RtlNtStatusToDosError( status ) );
212             break;
213         }
214     }
215 }
216
217 static void fill_process( struct snapshot* snap, ULONG* offset, 
218                           SYSTEM_PROCESS_INFORMATION* spi, ULONG num )
219 {
220     PROCESSENTRY32W*            pcs_entry;
221     ULONG                       poff = 0;
222     SIZE_T                      l;
223
224     snap->process_count = num;
225     snap->process_pos = 0;
226     if (!num) return;
227     snap->process_offset = *offset;
228
229     pcs_entry = (PROCESSENTRY32W*)&snap->data[*offset];
230
231     do
232     {
233         spi = (SYSTEM_PROCESS_INFORMATION*)((char*)spi + poff);
234
235         pcs_entry->dwSize = sizeof(PROCESSENTRY32W);
236         pcs_entry->cntUsage = 0; /* MSDN says no longer used, always 0 */
237         pcs_entry->th32ProcessID = HandleToUlong(spi->UniqueProcessId);
238         pcs_entry->th32DefaultHeapID = 0; /* MSDN says no longer used, always 0 */
239         pcs_entry->th32ModuleID = 0; /* MSDN says no longer used, always 0 */
240         pcs_entry->cntThreads = spi->dwThreadCount;
241         pcs_entry->th32ParentProcessID = HandleToUlong(spi->ParentProcessId);
242         pcs_entry->pcPriClassBase = spi->dwBasePriority;
243         pcs_entry->dwFlags = 0; /* MSDN says no longer used, always 0 */
244         l = min(spi->ProcessName.Length, sizeof(pcs_entry->szExeFile) - sizeof(WCHAR));
245         memcpy(pcs_entry->szExeFile, spi->ProcessName.Buffer, l);
246         pcs_entry->szExeFile[l / sizeof(WCHAR)] = '\0';
247         pcs_entry++;
248     } while ((poff = spi->NextEntryOffset));
249
250     *offset += num * sizeof(PROCESSENTRY32W);
251 }
252
253 static void fill_thread( struct snapshot* snap, ULONG* offset, LPVOID info, ULONG num )
254 {
255     THREADENTRY32*              thd_entry;
256     SYSTEM_PROCESS_INFORMATION* spi;
257     SYSTEM_THREAD_INFORMATION*  sti;
258     ULONG                       i, poff = 0;
259
260     snap->thread_count = num;
261     snap->thread_pos = 0;
262     if (!num) return;
263     snap->thread_offset = *offset;
264
265     thd_entry = (THREADENTRY32*)&snap->data[*offset];
266
267     spi = info;
268     do
269     {
270         spi = (SYSTEM_PROCESS_INFORMATION*)((char*)spi + poff);
271         sti = &spi->ti[0];
272
273         for (i = 0; i < spi->dwThreadCount; i++)
274         {
275             thd_entry->dwSize = sizeof(THREADENTRY32);
276             thd_entry->cntUsage = 0; /* MSDN says no longer used, always 0 */
277             thd_entry->th32ThreadID = HandleToUlong(sti->ClientId.UniqueThread);
278             thd_entry->th32OwnerProcessID = HandleToUlong(sti->ClientId.UniqueProcess);
279             thd_entry->tpBasePri = sti->dwBasePriority;
280             thd_entry->tpDeltaPri = 0; /* MSDN says no longer used, always 0 */
281             thd_entry->dwFlags = 0; /* MSDN says no longer used, always 0" */
282
283             sti++;
284             thd_entry++;
285       }
286     } while ((poff = spi->NextEntryOffset));
287     *offset += num * sizeof(THREADENTRY32);
288 }
289
290 /***********************************************************************
291  *           CreateToolhelp32Snapshot                   (KERNEL32.@)
292  */
293 HANDLE WINAPI CreateToolhelp32Snapshot( DWORD flags, DWORD process )
294 {
295     SYSTEM_PROCESS_INFORMATION* spi = NULL;
296     LDR_MODULE*         mod = NULL;
297     ULONG               num_pcs, num_thd, num_mod;
298     HANDLE              hSnapShot = 0;
299
300     TRACE("%x,%x\n", flags, process );
301     if (!(flags & (TH32CS_SNAPPROCESS|TH32CS_SNAPTHREAD|TH32CS_SNAPMODULE)))
302     {
303         FIXME("flags %x not implemented\n", flags );
304         SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
305         return INVALID_HANDLE_VALUE;
306     }
307
308     if (fetch_module( process, flags, &mod, &num_mod ) &&
309         fetch_process_thread( flags, &spi, &num_pcs, &num_thd ))
310     {
311         ULONG sect_size;
312         struct snapshot*snap;
313         SECURITY_ATTRIBUTES sa;
314
315         /* create & fill the snapshot section */
316         sect_size = sizeof(struct snapshot) - 1; /* for last data[1] */
317         if (flags & TH32CS_SNAPMODULE)  sect_size += num_mod * sizeof(MODULEENTRY32W);
318         if (flags & TH32CS_SNAPPROCESS) sect_size += num_pcs * sizeof(PROCESSENTRY32W);
319         if (flags & TH32CS_SNAPTHREAD)  sect_size += num_thd * sizeof(THREADENTRY32);
320         if (flags & TH32CS_SNAPHEAPLIST)FIXME("Unimplemented: heap list snapshot\n");
321
322         sa.bInheritHandle = (flags & TH32CS_INHERIT) != 0;
323         sa.lpSecurityDescriptor = NULL;
324
325         hSnapShot = CreateFileMappingW( INVALID_HANDLE_VALUE, &sa,
326                                         SEC_COMMIT | PAGE_READWRITE,
327                                         0, sect_size, NULL );
328         if (hSnapShot && (snap = MapViewOfFile( hSnapShot, FILE_MAP_ALL_ACCESS, 0, 0, 0 )))
329         {
330             DWORD   offset = 0;
331
332             fill_module( snap, &offset, process, mod, num_mod );
333             fill_process( snap, &offset, spi, num_pcs );
334             fill_thread( snap, &offset, spi, num_thd );
335             UnmapViewOfFile( snap );
336         }
337     }
338
339     while (num_mod--)
340     {
341         HeapFree( GetProcessHeap(), 0, mod[num_mod].BaseDllName.Buffer );
342         HeapFree( GetProcessHeap(), 0, mod[num_mod].FullDllName.Buffer );
343     }
344     HeapFree( GetProcessHeap(), 0, mod );
345     HeapFree( GetProcessHeap(), 0, spi );
346
347     if (!hSnapShot) return INVALID_HANDLE_VALUE;
348     return hSnapShot;
349 }
350
351 static BOOL next_thread( HANDLE hSnapShot, LPTHREADENTRY32 lpte, BOOL first )
352 {
353     struct snapshot*    snap;
354     BOOL                ret = FALSE;
355
356     if (lpte->dwSize < sizeof(THREADENTRY32))
357     {
358         SetLastError( ERROR_INSUFFICIENT_BUFFER );
359         WARN("Result buffer too small (%d)\n", lpte->dwSize);
360         return FALSE;
361     }
362     if ((snap = MapViewOfFile( hSnapShot, FILE_MAP_ALL_ACCESS, 0, 0, 0 )))
363     {
364         if (first) snap->thread_pos = 0;
365         if (snap->thread_pos < snap->thread_count)
366         {
367             LPTHREADENTRY32 te = (THREADENTRY32*)&snap->data[snap->thread_offset];
368             *lpte = te[snap->thread_pos++];
369             ret = TRUE;
370         }
371         else SetLastError( ERROR_NO_MORE_FILES );
372         UnmapViewOfFile( snap );
373     }
374     return ret;
375 }
376
377 /***********************************************************************
378  *              Thread32First    (KERNEL32.@)
379  *
380  * Return info about the first thread in a toolhelp32 snapshot
381  */
382 BOOL WINAPI Thread32First( HANDLE hSnapShot, LPTHREADENTRY32 lpte )
383 {
384     return next_thread( hSnapShot, lpte, TRUE );
385 }
386
387 /***********************************************************************
388  *              Thread32Next    (KERNEL32.@)
389  *
390  * Return info about the first thread in a toolhelp32 snapshot
391  */
392 BOOL WINAPI Thread32Next( HANDLE hSnapShot, LPTHREADENTRY32 lpte )
393 {
394     return next_thread( hSnapShot, lpte, FALSE );
395 }
396
397 /***********************************************************************
398  *              process_next
399  *
400  * Implementation of Process32First/Next. Note that the ANSI / Unicode
401  * version check is a bit of a hack as it relies on the fact that only
402  * the last field is actually different.
403  */
404 static BOOL process_next( HANDLE hSnapShot, LPPROCESSENTRY32W lppe,
405                           BOOL first, BOOL unicode )
406 {
407     struct snapshot*    snap;
408     BOOL                ret = FALSE;
409     DWORD               sz = unicode ? sizeof(PROCESSENTRY32W) : sizeof(PROCESSENTRY32);
410
411     if (lppe->dwSize < sz)
412     {
413         SetLastError( ERROR_INSUFFICIENT_BUFFER );
414         WARN("Result buffer too small (%d)\n", lppe->dwSize);
415         return FALSE;
416     }
417     if ((snap = MapViewOfFile( hSnapShot, FILE_MAP_ALL_ACCESS, 0, 0, 0 )))
418     {
419         if (first) snap->process_pos = 0;
420         if (snap->process_pos < snap->process_count)
421         {
422             LPPROCESSENTRY32W pe = (PROCESSENTRY32W*)&snap->data[snap->process_offset];
423             if (unicode)
424                 *lppe = pe[snap->process_pos];
425             else
426             {
427                 lppe->cntUsage = pe[snap->process_pos].cntUsage;
428                 lppe->th32ProcessID = pe[snap->process_pos].th32ProcessID;
429                 lppe->th32DefaultHeapID = pe[snap->process_pos].th32DefaultHeapID;
430                 lppe->th32ModuleID = pe[snap->process_pos].th32ModuleID;
431                 lppe->cntThreads = pe[snap->process_pos].cntThreads;
432                 lppe->th32ParentProcessID = pe[snap->process_pos].th32ParentProcessID;
433                 lppe->pcPriClassBase = pe[snap->process_pos].pcPriClassBase;
434                 lppe->dwFlags = pe[snap->process_pos].dwFlags;
435
436                 WideCharToMultiByte( CP_ACP, 0, pe[snap->process_pos].szExeFile, -1,
437                                      (char*)lppe->szExeFile, sizeof(lppe->szExeFile),
438                                      0, 0 );
439             }
440             snap->process_pos++;
441             ret = TRUE;
442         }
443         else SetLastError( ERROR_NO_MORE_FILES );
444         UnmapViewOfFile( snap );
445     }
446
447     return ret;
448 }
449
450
451 /***********************************************************************
452  *              Process32First    (KERNEL32.@)
453  *
454  * Return info about the first process in a toolhelp32 snapshot
455  */
456 BOOL WINAPI Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe)
457 {
458     return process_next( hSnapshot, (PROCESSENTRY32W*)lppe, TRUE, FALSE /* ANSI */ );
459 }
460
461 /***********************************************************************
462  *              Process32Next   (KERNEL32.@)
463  *
464  * Return info about the "next" process in a toolhelp32 snapshot
465  */
466 BOOL WINAPI Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe)
467 {
468     return process_next( hSnapshot, (PROCESSENTRY32W*)lppe, FALSE, FALSE /* ANSI */ );
469 }
470
471 /***********************************************************************
472  *              Process32FirstW    (KERNEL32.@)
473  *
474  * Return info about the first process in a toolhelp32 snapshot
475  */
476 BOOL WINAPI Process32FirstW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe)
477 {
478     return process_next( hSnapshot, lppe, TRUE, TRUE /* Unicode */ );
479 }
480
481 /***********************************************************************
482  *              Process32NextW   (KERNEL32.@)
483  *
484  * Return info about the "next" process in a toolhelp32 snapshot
485  */
486 BOOL WINAPI Process32NextW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe)
487 {
488     return process_next( hSnapshot, lppe, FALSE, TRUE /* Unicode */ );
489 }
490
491 /***********************************************************************
492  *              module_nextW
493  *
494  * Implementation of Module32{First|Next}W
495  */
496 static BOOL module_nextW( HANDLE hSnapShot, LPMODULEENTRY32W lpme, BOOL first )
497 {
498     struct snapshot*    snap;
499     BOOL                ret = FALSE;
500
501     if (lpme->dwSize < sizeof (MODULEENTRY32W))
502     {
503         SetLastError( ERROR_INSUFFICIENT_BUFFER );
504         WARN("Result buffer too small (was: %d)\n", lpme->dwSize);
505         return FALSE;
506     }
507     if ((snap = MapViewOfFile( hSnapShot, FILE_MAP_ALL_ACCESS, 0, 0, 0 )))
508     {
509         if (first) snap->module_pos = 0;
510         if (snap->module_pos < snap->module_count)
511         {
512             LPMODULEENTRY32W pe = (MODULEENTRY32W*)&snap->data[snap->module_offset];
513             *lpme = pe[snap->module_pos++];
514             ret = TRUE;
515         }
516         else SetLastError( ERROR_NO_MORE_FILES );
517         UnmapViewOfFile( snap );
518     }
519
520     return ret;
521 }
522
523 /***********************************************************************
524  *              Module32FirstW   (KERNEL32.@)
525  *
526  * Return info about the "first" module in a toolhelp32 snapshot
527  */
528 BOOL WINAPI Module32FirstW(HANDLE hSnapshot, LPMODULEENTRY32W lpme)
529 {
530     return module_nextW( hSnapshot, lpme, TRUE );
531 }
532
533 /***********************************************************************
534  *              Module32NextW   (KERNEL32.@)
535  *
536  * Return info about the "next" module in a toolhelp32 snapshot
537  */
538 BOOL WINAPI Module32NextW(HANDLE hSnapshot, LPMODULEENTRY32W lpme)
539 {
540     return module_nextW( hSnapshot, lpme, FALSE );
541 }
542
543 /***********************************************************************
544  *              module_nextA
545  *
546  * Implementation of Module32{First|Next}A
547  */
548 static BOOL module_nextA( HANDLE handle, LPMODULEENTRY32 lpme, BOOL first )
549 {
550     BOOL ret;
551     MODULEENTRY32W mew;
552
553     if (lpme->dwSize < sizeof(MODULEENTRY32))
554     {
555         SetLastError( ERROR_INSUFFICIENT_BUFFER );
556         WARN("Result buffer too small (was: %d)\n", lpme->dwSize);
557         return FALSE;
558     }
559
560     mew.dwSize = sizeof(mew);
561     if ((ret = module_nextW( handle, &mew, first )))
562     {
563         lpme->th32ModuleID  = mew.th32ModuleID;
564         lpme->th32ProcessID = mew.th32ProcessID;
565         lpme->GlblcntUsage  = mew.GlblcntUsage;
566         lpme->ProccntUsage  = mew.ProccntUsage;
567         lpme->modBaseAddr   = mew.modBaseAddr;
568         lpme->modBaseSize   = mew.modBaseSize;
569         lpme->hModule       = mew.hModule;
570         WideCharToMultiByte( CP_ACP, 0, mew.szModule, -1, lpme->szModule, sizeof(lpme->szModule), NULL, NULL );
571         WideCharToMultiByte( CP_ACP, 0, mew.szExePath, -1, lpme->szExePath, sizeof(lpme->szExePath), NULL, NULL );
572     }
573     return ret;
574 }
575
576 /***********************************************************************
577  *              Module32First   (KERNEL32.@)
578  *
579  * Return info about the "first" module in a toolhelp32 snapshot
580  */
581 BOOL WINAPI Module32First(HANDLE hSnapshot, LPMODULEENTRY32 lpme)
582 {
583     return module_nextA( hSnapshot, lpme, TRUE );
584 }
585
586 /***********************************************************************
587  *              Module32Next   (KERNEL32.@)
588  *
589  * Return info about the "next" module in a toolhelp32 snapshot
590  */
591 BOOL WINAPI Module32Next(HANDLE hSnapshot, LPMODULEENTRY32 lpme)
592 {
593     return module_nextA( hSnapshot, lpme, FALSE );
594 }
595
596 /************************************************************************
597  *              Heap32ListFirst (KERNEL32.@)
598  *
599  */
600 BOOL WINAPI Heap32ListFirst(HANDLE hSnapshot, LPHEAPLIST32 lphl)
601 {
602     FIXME(": stub\n");
603     return FALSE;
604 }
605
606 /******************************************************************
607  *              Toolhelp32ReadProcessMemory (KERNEL32.@)
608  *
609  *
610  */
611 BOOL WINAPI Toolhelp32ReadProcessMemory(DWORD pid, const void* base,
612                                         void* buf, SIZE_T len, SIZE_T* r)
613 {
614     HANDLE h;
615     BOOL   ret = FALSE;
616
617     h = (pid) ? OpenProcess(PROCESS_VM_READ, FALSE, pid) : GetCurrentProcess();
618     if (h != NULL)
619     {
620         ret = ReadProcessMemory(h, base, buf, len, r);
621         if (pid) CloseHandle(h);
622     }
623     return ret;
624 }