xref: /reactos/dll/win32/dbghelp/dbghelp.c (revision 19b18ce2)
1 /*
2  * File dbghelp.c - generic routines (process) for dbghelp DLL
3  *
4  * Copyright (C) 2004, Eric Pouech
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #ifndef DBGHELP_STATIC_LIB
22 #include "config.h"
23 
24 #include "dbghelp_private.h"
25 #include "winerror.h"
26 #include "psapi.h"
27 #include "wine/debug.h"
28 #include "wdbgexts.h"
29 #include "winnls.h"
30 #else
31 #include "dbghelp_private.h"
32 #include "wdbgexts.h"
33 #endif
34 
35 WINE_DEFAULT_DEBUG_CHANNEL(dbghelp);
36 
37 /* TODO
38  *  - support for symbols' types is still partly missing
39  *      + C++ support
40  *      + we should store the underlying type for an enum in the symt_enum struct
41  *      + for enums, we store the names & values (associated to the enum type),
42  *        but those values are not directly usable from a debugger (that's why, I
43  *        assume, that we have also to define constants for enum values, as
44  *        Codeview does BTW.
45  *      + SymEnumTypes should only return *user* defined types (UDT, typedefs...) not
46  *        all the types stored/used in the modules (like char*)
47  *  - SymGetLine{Next|Prev} don't work as expected (they don't seem to work across
48  *    functions, and even across function blocks...). Basically, for *Next* to work
49  *    it requires an address after the prolog of the func (the base address of the
50  *    func doesn't work)
51  *  - most options (dbghelp_options) are not used (loading lines...)
52  *  - in symbol lookup by name, we don't use RE everywhere we should. Moreover, when
53  *    we're supposed to use RE, it doesn't make use of our hash tables. Therefore,
54  *    we could use hash if name isn't a RE, and fall back to a full search when we
55  *    get a full RE
56  *  - msc:
57  *      + we should add parameters' types to the function's signature
58  *        while processing a function's parameters
59  *      + add support for function-less labels (as MSC seems to define them)
60  *      + C++ management
61  *  - stabs:
62  *      + when, in a same module, the same definition is used in several compilation
63  *        units, we get several definitions of the same object (especially
64  *        struct/union). we should find a way not to duplicate them
65  *      + in some cases (dlls/user/dialog16.c DIALOG_GetControl16), the same static
66  *        global variable is defined several times (at different scopes). We are
67  *        getting several of those while looking for a unique symbol. Part of the
68  *        issue is that we don't give a scope to a static variable inside a function
69  *      + C++ management
70  */
71 
72 unsigned   dbghelp_options = SYMOPT_UNDNAME;
73 
74 static struct process* process_first /* = NULL */;
75 
76 /******************************************************************
77  *		process_find_by_handle
78  *
79  */
80 struct process*    process_find_by_handle(HANDLE hProcess)
81 {
82     struct process* p;
83 
84     for (p = process_first; p && p->handle != hProcess; p = p->next);
85     if (!p) SetLastError(ERROR_INVALID_HANDLE);
86     return p;
87 }
88 
89 /******************************************************************
90  *             validate_addr64 (internal)
91  *
92  */
93 BOOL validate_addr64(DWORD64 addr)
94 {
95     if (sizeof(void*) == sizeof(int) && (addr >> 32))
96     {
97         FIXME("Unsupported address %s\n", wine_dbgstr_longlong(addr));
98         SetLastError(ERROR_INVALID_PARAMETER);
99         return FALSE;
100     }
101     return TRUE;
102 }
103 
104 /******************************************************************
105  *		fetch_buffer
106  *
107  * Ensures process' internal buffer is large enough.
108  */
109 void* fetch_buffer(struct process* pcs, unsigned size)
110 {
111     if (size > pcs->buffer_size)
112     {
113         if (pcs->buffer)
114             pcs->buffer = HeapReAlloc(GetProcessHeap(), 0, pcs->buffer, size);
115         else
116             pcs->buffer = HeapAlloc(GetProcessHeap(), 0, size);
117         pcs->buffer_size = (pcs->buffer) ? size : 0;
118     }
119     return pcs->buffer;
120 }
121 
122 #ifndef DBGHELP_STATIC_LIB
123 const char* wine_dbgstr_addr(const ADDRESS64* addr)
124 {
125     if (!addr) return "(null)";
126     switch (addr->Mode)
127     {
128     case AddrModeFlat:
129         return wine_dbg_sprintf("flat<%s>", wine_dbgstr_longlong(addr->Offset));
130     case AddrMode1616:
131         return wine_dbg_sprintf("1616<%04x:%04x>", addr->Segment, (DWORD)addr->Offset);
132     case AddrMode1632:
133         return wine_dbg_sprintf("1632<%04x:%08x>", addr->Segment, (DWORD)addr->Offset);
134     case AddrModeReal:
135         return wine_dbg_sprintf("real<%04x:%04x>", addr->Segment, (DWORD)addr->Offset);
136     default:
137         return "unknown";
138     }
139 }
140 #endif
141 
142 extern struct cpu       cpu_i386, cpu_x86_64, cpu_ppc, cpu_arm, cpu_arm64;
143 
144 #ifndef DBGHELP_STATIC_LIB
145 static struct cpu*      dbghelp_cpus[] = {&cpu_i386, &cpu_x86_64, &cpu_ppc, &cpu_arm, &cpu_arm64, NULL};
146 #else
147 static struct cpu*      dbghelp_cpus[] = {&cpu_i386, NULL};
148 #endif
149 
150 struct cpu*             dbghelp_current_cpu =
151 #if defined(__i386__) || defined(DBGHELP_STATIC_LIB)
152     &cpu_i386
153 #elif defined(__x86_64__)
154     &cpu_x86_64
155 #elif defined(__powerpc__)
156     &cpu_ppc
157 #elif defined(__arm__)
158     &cpu_arm
159 #elif defined(__aarch64__)
160     &cpu_arm64
161 #else
162 #error define support for your CPU
163 #endif
164     ;
165 
166 struct cpu* cpu_find(DWORD machine)
167 {
168     struct cpu** cpu;
169 
170     for (cpu = dbghelp_cpus ; *cpu; cpu++)
171     {
172         if (cpu[0]->machine == machine) return cpu[0];
173     }
174     return NULL;
175 }
176 
177 /******************************************************************
178  *		SymSetSearchPathW (DBGHELP.@)
179  *
180  */
181 BOOL WINAPI SymSetSearchPathW(HANDLE hProcess, PCWSTR searchPath)
182 {
183     struct process* pcs = process_find_by_handle(hProcess);
184 
185     if (!pcs) return FALSE;
186     if (!searchPath) return FALSE;
187 
188     HeapFree(GetProcessHeap(), 0, pcs->search_path);
189     pcs->search_path = lstrcpyW(HeapAlloc(GetProcessHeap(), 0,
190                                           (lstrlenW(searchPath) + 1) * sizeof(WCHAR)),
191                                 searchPath);
192     return TRUE;
193 }
194 
195 /******************************************************************
196  *		SymSetSearchPath (DBGHELP.@)
197  *
198  */
199 BOOL WINAPI SymSetSearchPath(HANDLE hProcess, PCSTR searchPath)
200 {
201     BOOL        ret = FALSE;
202     unsigned    len;
203     WCHAR*      sp;
204 
205     len = MultiByteToWideChar(CP_ACP, 0, searchPath, -1, NULL, 0);
206     if ((sp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR))))
207     {
208         MultiByteToWideChar(CP_ACP, 0, searchPath, -1, sp, len);
209 
210         ret = SymSetSearchPathW(hProcess, sp);
211         HeapFree(GetProcessHeap(), 0, sp);
212     }
213     return ret;
214 }
215 
216 /***********************************************************************
217  *		SymGetSearchPathW (DBGHELP.@)
218  */
219 BOOL WINAPI SymGetSearchPathW(HANDLE hProcess, PWSTR szSearchPath,
220                               DWORD SearchPathLength)
221 {
222     struct process* pcs = process_find_by_handle(hProcess);
223     if (!pcs) return FALSE;
224 
225     lstrcpynW(szSearchPath, pcs->search_path, SearchPathLength);
226     return TRUE;
227 }
228 
229 /***********************************************************************
230  *		SymGetSearchPath (DBGHELP.@)
231  */
232 BOOL WINAPI SymGetSearchPath(HANDLE hProcess, PSTR szSearchPath,
233                              DWORD SearchPathLength)
234 {
235     WCHAR*      buffer = HeapAlloc(GetProcessHeap(), 0, SearchPathLength * sizeof(WCHAR));
236     BOOL        ret = FALSE;
237 
238     if (buffer)
239     {
240         ret = SymGetSearchPathW(hProcess, buffer, SearchPathLength);
241         if (ret)
242             WideCharToMultiByte(CP_ACP, 0, buffer, SearchPathLength,
243                                 szSearchPath, SearchPathLength, NULL, NULL);
244         HeapFree(GetProcessHeap(), 0, buffer);
245     }
246     return ret;
247 }
248 
249 /******************************************************************
250  *		invade_process
251  *
252  * SymInitialize helper: loads in dbghelp all known (and loaded modules)
253  * this assumes that hProcess is a handle on a valid process
254  */
255 static BOOL WINAPI process_invade_cb(PCWSTR name, ULONG64 base, ULONG size, PVOID user)
256 {
257     WCHAR       tmp[MAX_PATH];
258     HANDLE      hProcess = user;
259 
260     if (!GetModuleFileNameExW(hProcess, (HMODULE)(DWORD_PTR)base,
261 			      tmp, sizeof(tmp) / sizeof(WCHAR)))
262         lstrcpynW(tmp, name, sizeof(tmp) / sizeof(WCHAR));
263 
264     SymLoadModuleExW(hProcess, 0, tmp, name, base, size, NULL, 0);
265     return TRUE;
266 }
267 
268 /******************************************************************
269  *		check_live_target
270  *
271  */
272 static BOOL check_live_target(struct process* pcs)
273 {
274     if (!GetProcessId(pcs->handle)) return FALSE;
275     if (GetEnvironmentVariableA("DBGHELP_NOLIVE", NULL, 0)) return FALSE;
276 #ifndef DBGHELP_STATIC_LIB
277     if (!elf_read_wine_loader_dbg_info(pcs))
278         macho_read_wine_loader_dbg_info(pcs);
279 #endif
280     return TRUE;
281 }
282 
283 /******************************************************************
284  *		SymInitializeW (DBGHELP.@)
285  *
286  * The initialisation of a dbghelp's context.
287  * Note that hProcess doesn't need to be a valid process handle (except
288  * when fInvadeProcess is TRUE).
289  * Since we also allow loading ELF (pure) libraries and Wine ELF libraries
290  * containing PE (and NE) module(s), here's how we handle it:
291  * - we load every module (ELF, NE, PE) passed in SymLoadModule
292  * - in fInvadeProcess (in SymInitialize) is TRUE, we set up what is called ELF
293  *   synchronization: hProcess should be a valid process handle, and we hook
294  *   ourselves on hProcess's loaded ELF-modules, and keep this list in sync with
295  *   our internal ELF modules representation (loading / unloading). This way,
296  *   we'll pair every loaded builtin PE module with its ELF counterpart (and
297  *   access its debug information).
298  * - if fInvadeProcess (in SymInitialize) is FALSE, we check anyway if the
299  *   hProcess refers to a running process. We use some heuristics here, so YMMV.
300  *   If we detect a live target, then we get the same handling as if
301  *   fInvadeProcess is TRUE (except that the modules are not loaded). Otherwise,
302  *   we won't be able to make the peering between a builtin PE module and its ELF
303  *   counterpart. Hence we won't be able to provide the requested debug
304  *   information. We'll however be able to load native PE modules (and their
305  *   debug information) without any trouble.
306  * Note also that this scheme can be intertwined with the deferred loading
307  * mechanism (ie only load the debug information when we actually need it).
308  */
309 BOOL WINAPI SymInitializeW(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess)
310 {
311     struct process*     pcs;
312 
313     TRACE("(%p %s %u)\n", hProcess, debugstr_w(UserSearchPath), fInvadeProcess);
314 
315     if (process_find_by_handle(hProcess)){
316         WARN("the symbols for this process have already been initialized!\n");
317 
318         /* MSDN says to only call this function once unless SymCleanup() has been called since the last call.
319            It also says to call SymRefreshModuleList() instead if you just want the module list refreshed.
320            Native still returns TRUE even if the process has already been initialized. */
321         return TRUE;
322     }
323 
324     pcs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pcs));
325     if (!pcs) return FALSE;
326 
327     pcs->handle = hProcess;
328 
329     if (UserSearchPath)
330     {
331         pcs->search_path = lstrcpyW(HeapAlloc(GetProcessHeap(), 0,
332                                               (lstrlenW(UserSearchPath) + 1) * sizeof(WCHAR)),
333                                     UserSearchPath);
334     }
335     else
336     {
337         unsigned        size;
338         unsigned        len;
339         static const WCHAR      sym_path[] = {'_','N','T','_','S','Y','M','B','O','L','_','P','A','T','H',0};
340         static const WCHAR      alt_sym_path[] = {'_','N','T','_','A','L','T','E','R','N','A','T','E','_','S','Y','M','B','O','L','_','P','A','T','H',0};
341 
342         pcs->search_path = HeapAlloc(GetProcessHeap(), 0, (len = MAX_PATH) * sizeof(WCHAR));
343         while ((size = GetCurrentDirectoryW(len, pcs->search_path)) >= len)
344             pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (len *= 2) * sizeof(WCHAR));
345         pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (size + 1) * sizeof(WCHAR));
346 
347         len = GetEnvironmentVariableW(sym_path, NULL, 0);
348         if (len)
349         {
350             pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (size + 1 + len + 1) * sizeof(WCHAR));
351             pcs->search_path[size] = ';';
352             GetEnvironmentVariableW(sym_path, pcs->search_path + size + 1, len);
353             size += 1 + len;
354         }
355         len = GetEnvironmentVariableW(alt_sym_path, NULL, 0);
356         if (len)
357         {
358             pcs->search_path = HeapReAlloc(GetProcessHeap(), 0, pcs->search_path, (size + 1 + len + 1) * sizeof(WCHAR));
359             pcs->search_path[size] = ';';
360             GetEnvironmentVariableW(alt_sym_path, pcs->search_path + size + 1, len);
361         }
362     }
363 
364     pcs->lmodules = NULL;
365     pcs->dbg_hdr_addr = 0;
366     pcs->next = process_first;
367     process_first = pcs;
368 
369 #ifndef DBGHELP_STATIC_LIB
370     if (check_live_target(pcs))
371     {
372         if (fInvadeProcess)
373             EnumerateLoadedModulesW64(hProcess, process_invade_cb, hProcess);
374         elf_synchronize_module_list(pcs);
375         macho_synchronize_module_list(pcs);
376     }
377     else if (fInvadeProcess)
378     {
379         SymCleanup(hProcess);
380         SetLastError(ERROR_INVALID_PARAMETER);
381         return FALSE;
382     }
383 #endif
384 
385     return TRUE;
386 }
387 
388 /******************************************************************
389  *		SymInitialize (DBGHELP.@)
390  *
391  *
392  */
393 BOOL WINAPI SymInitialize(HANDLE hProcess, PCSTR UserSearchPath, BOOL fInvadeProcess)
394 {
395     WCHAR*              sp = NULL;
396     BOOL                ret;
397 
398     if (UserSearchPath)
399     {
400         unsigned len;
401 
402         len = MultiByteToWideChar(CP_ACP, 0, UserSearchPath, -1, NULL, 0);
403         sp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
404         MultiByteToWideChar(CP_ACP, 0, UserSearchPath, -1, sp, len);
405     }
406 
407     ret = SymInitializeW(hProcess, sp, fInvadeProcess);
408     HeapFree(GetProcessHeap(), 0, sp);
409     return ret;
410 }
411 
412 /******************************************************************
413  *		SymCleanup (DBGHELP.@)
414  *
415  */
416 BOOL WINAPI SymCleanup(HANDLE hProcess)
417 {
418     struct process**    ppcs;
419     struct process*     next;
420 
421     for (ppcs = &process_first; *ppcs; ppcs = &(*ppcs)->next)
422     {
423         if ((*ppcs)->handle == hProcess)
424         {
425             while ((*ppcs)->lmodules) module_remove(*ppcs, (*ppcs)->lmodules);
426 
427             HeapFree(GetProcessHeap(), 0, (*ppcs)->search_path);
428             next = (*ppcs)->next;
429             HeapFree(GetProcessHeap(), 0, *ppcs);
430             *ppcs = next;
431             return TRUE;
432         }
433     }
434 
435     ERR("this process has not had SymInitialize() called for it!\n");
436     return FALSE;
437 }
438 
439 /******************************************************************
440  *		SymSetOptions (DBGHELP.@)
441  *
442  */
443 DWORD WINAPI SymSetOptions(DWORD opts)
444 {
445     struct process* pcs;
446 
447     for (pcs = process_first; pcs; pcs = pcs->next)
448     {
449         pcs_callback(pcs, CBA_SET_OPTIONS, &opts);
450     }
451     return dbghelp_options = opts;
452 }
453 
454 /******************************************************************
455  *		SymGetOptions (DBGHELP.@)
456  *
457  */
458 DWORD WINAPI SymGetOptions(void)
459 {
460     return dbghelp_options;
461 }
462 
463 /******************************************************************
464  *		SymSetParentWindow (DBGHELP.@)
465  *
466  */
467 BOOL WINAPI SymSetParentWindow(HWND hwnd)
468 {
469     /* Save hwnd so it can be used as parent window */
470     FIXME("(%p): stub\n", hwnd);
471     return TRUE;
472 }
473 
474 /******************************************************************
475  *		SymSetContext (DBGHELP.@)
476  *
477  */
478 BOOL WINAPI SymSetContext(HANDLE hProcess, PIMAGEHLP_STACK_FRAME StackFrame,
479                           PIMAGEHLP_CONTEXT Context)
480 {
481     struct process* pcs = process_find_by_handle(hProcess);
482     if (!pcs) return FALSE;
483 
484     if (pcs->ctx_frame.ReturnOffset == StackFrame->ReturnOffset &&
485         pcs->ctx_frame.FrameOffset  == StackFrame->FrameOffset  &&
486         pcs->ctx_frame.StackOffset  == StackFrame->StackOffset)
487     {
488         TRACE("Setting same frame {rtn=%s frm=%s stk=%s}\n",
489               wine_dbgstr_longlong(pcs->ctx_frame.ReturnOffset),
490               wine_dbgstr_longlong(pcs->ctx_frame.FrameOffset),
491               wine_dbgstr_longlong(pcs->ctx_frame.StackOffset));
492         pcs->ctx_frame.InstructionOffset = StackFrame->InstructionOffset;
493         SetLastError(ERROR_ACCESS_DENIED); /* latest MSDN says ERROR_SUCCESS */
494         return FALSE;
495     }
496 
497     pcs->ctx_frame = *StackFrame;
498     /* MSDN states that Context is not (no longer?) used */
499     return TRUE;
500 }
501 
502 /******************************************************************
503  *		reg_cb64to32 (internal)
504  *
505  * Registered callback for converting information from 64 bit to 32 bit
506  */
507 static BOOL CALLBACK reg_cb64to32(HANDLE hProcess, ULONG action, ULONG64 data, ULONG64 user)
508 {
509     struct process*                     pcs = process_find_by_handle(hProcess);
510     void*                               data32;
511     IMAGEHLP_DEFERRED_SYMBOL_LOAD64*    idsl64;
512     IMAGEHLP_DEFERRED_SYMBOL_LOAD       idsl;
513 
514     if (!pcs) return FALSE;
515     switch (action)
516     {
517     case CBA_DEBUG_INFO:
518     case CBA_DEFERRED_SYMBOL_LOAD_CANCEL:
519     case CBA_SET_OPTIONS:
520     case CBA_SYMBOLS_UNLOADED:
521         data32 = (void*)(DWORD_PTR)data;
522         break;
523     case CBA_DEFERRED_SYMBOL_LOAD_COMPLETE:
524     case CBA_DEFERRED_SYMBOL_LOAD_FAILURE:
525     case CBA_DEFERRED_SYMBOL_LOAD_PARTIAL:
526     case CBA_DEFERRED_SYMBOL_LOAD_START:
527         idsl64 = (IMAGEHLP_DEFERRED_SYMBOL_LOAD64*)(DWORD_PTR)data;
528         if (!validate_addr64(idsl64->BaseOfImage))
529             return FALSE;
530         idsl.SizeOfStruct = sizeof(idsl);
531         idsl.BaseOfImage = (DWORD)idsl64->BaseOfImage;
532         idsl.CheckSum = idsl64->CheckSum;
533         idsl.TimeDateStamp = idsl64->TimeDateStamp;
534         memcpy(idsl.FileName, idsl64->FileName, sizeof(idsl.FileName));
535         idsl.Reparse = idsl64->Reparse;
536         data32 = &idsl;
537         break;
538     case CBA_DUPLICATE_SYMBOL:
539     case CBA_EVENT:
540     case CBA_READ_MEMORY:
541     default:
542         FIXME("No mapping for action %u\n", action);
543         return FALSE;
544     }
545     return pcs->reg_cb32(hProcess, action, data32, (PVOID)(DWORD_PTR)user);
546 }
547 
548 /******************************************************************
549  *		pcs_callback (internal)
550  */
551 BOOL pcs_callback(const struct process* pcs, ULONG action, void* data)
552 {
553     IMAGEHLP_DEFERRED_SYMBOL_LOAD64 idsl;
554 
555     TRACE("%p %u %p\n", pcs, action, data);
556 
557     if (!pcs->reg_cb) return FALSE;
558     if (!pcs->reg_is_unicode)
559     {
560         IMAGEHLP_DEFERRED_SYMBOL_LOADW64*   idslW;
561 
562         switch (action)
563         {
564         case CBA_DEBUG_INFO:
565         case CBA_DEFERRED_SYMBOL_LOAD_CANCEL:
566         case CBA_SET_OPTIONS:
567         case CBA_SYMBOLS_UNLOADED:
568             break;
569         case CBA_DEFERRED_SYMBOL_LOAD_COMPLETE:
570         case CBA_DEFERRED_SYMBOL_LOAD_FAILURE:
571         case CBA_DEFERRED_SYMBOL_LOAD_PARTIAL:
572         case CBA_DEFERRED_SYMBOL_LOAD_START:
573             idslW = data;
574             idsl.SizeOfStruct = sizeof(idsl);
575             idsl.BaseOfImage = idslW->BaseOfImage;
576             idsl.CheckSum = idslW->CheckSum;
577             idsl.TimeDateStamp = idslW->TimeDateStamp;
578             WideCharToMultiByte(CP_ACP, 0, idslW->FileName, -1,
579                                 idsl.FileName, sizeof(idsl.FileName), NULL, NULL);
580             idsl.Reparse = idslW->Reparse;
581             data = &idsl;
582             break;
583         case CBA_DUPLICATE_SYMBOL:
584         case CBA_EVENT:
585         case CBA_READ_MEMORY:
586         default:
587             FIXME("No mapping for action %u\n", action);
588             return FALSE;
589         }
590     }
591     return pcs->reg_cb(pcs->handle, action, (ULONG64)(DWORD_PTR)data, pcs->reg_user);
592 }
593 
594 /******************************************************************
595  *		sym_register_cb
596  *
597  * Helper for registering a callback.
598  */
599 static BOOL sym_register_cb(HANDLE hProcess,
600                             PSYMBOL_REGISTERED_CALLBACK64 cb,
601                             PSYMBOL_REGISTERED_CALLBACK cb32,
602                             DWORD64 user, BOOL unicode)
603 {
604     struct process* pcs = process_find_by_handle(hProcess);
605 
606     if (!pcs) return FALSE;
607     pcs->reg_cb = cb;
608     pcs->reg_cb32 = cb32;
609     pcs->reg_is_unicode = unicode;
610     pcs->reg_user = user;
611 
612     return TRUE;
613 }
614 
615 /***********************************************************************
616  *		SymRegisterCallback (DBGHELP.@)
617  */
618 BOOL WINAPI SymRegisterCallback(HANDLE hProcess,
619                                 PSYMBOL_REGISTERED_CALLBACK CallbackFunction,
620                                 PVOID UserContext)
621 {
622     TRACE("(%p, %p, %p)\n",
623           hProcess, CallbackFunction, UserContext);
624     return sym_register_cb(hProcess, reg_cb64to32, CallbackFunction, (DWORD_PTR)UserContext, FALSE);
625 }
626 
627 /***********************************************************************
628  *		SymRegisterCallback64 (DBGHELP.@)
629  */
630 BOOL WINAPI SymRegisterCallback64(HANDLE hProcess,
631                                   PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction,
632                                   ULONG64 UserContext)
633 {
634     TRACE("(%p, %p, %s)\n",
635           hProcess, CallbackFunction, wine_dbgstr_longlong(UserContext));
636     return sym_register_cb(hProcess, CallbackFunction, NULL, UserContext, FALSE);
637 }
638 
639 /***********************************************************************
640  *		SymRegisterCallbackW64 (DBGHELP.@)
641  */
642 BOOL WINAPI SymRegisterCallbackW64(HANDLE hProcess,
643                                    PSYMBOL_REGISTERED_CALLBACK64 CallbackFunction,
644                                    ULONG64 UserContext)
645 {
646     TRACE("(%p, %p, %s)\n",
647           hProcess, CallbackFunction, wine_dbgstr_longlong(UserContext));
648     return sym_register_cb(hProcess, CallbackFunction, NULL, UserContext, TRUE);
649 }
650 
651 /* This is imagehlp version not dbghelp !! */
652 static API_VERSION api_version = { 4, 0, 2, 0 };
653 
654 /***********************************************************************
655  *           ImagehlpApiVersion (DBGHELP.@)
656  */
657 LPAPI_VERSION WINAPI ImagehlpApiVersion(VOID)
658 {
659     return &api_version;
660 }
661 
662 /***********************************************************************
663  *           ImagehlpApiVersionEx (DBGHELP.@)
664  */
665 LPAPI_VERSION WINAPI ImagehlpApiVersionEx(LPAPI_VERSION AppVersion)
666 {
667     if (!AppVersion) return NULL;
668 
669     AppVersion->MajorVersion = api_version.MajorVersion;
670     AppVersion->MinorVersion = api_version.MinorVersion;
671     AppVersion->Revision = api_version.Revision;
672     AppVersion->Reserved = api_version.Reserved;
673 
674     return AppVersion;
675 }
676 
677 /******************************************************************
678  *		ExtensionApiVersion (DBGHELP.@)
679  */
680 LPEXT_API_VERSION WINAPI ExtensionApiVersion(void)
681 {
682     static EXT_API_VERSION      eav = {5, 5, 5, 0};
683     return &eav;
684 }
685 
686 /******************************************************************
687  *		WinDbgExtensionDllInit (DBGHELP.@)
688  */
689 void WINAPI WinDbgExtensionDllInit(PWINDBG_EXTENSION_APIS lpExtensionApis,
690                                    unsigned short major, unsigned short minor)
691 {
692 }
693