winspool/tests: Use 0xdeadbeef as magic value.
[wine/multimedia.git] / programs / winedbg / info.c
blobb64a3dfa08ba908ae9f9b95566eb744551b6497c
1 /*
2 * Wine debugger utility routines
4 * Copyright 1993 Eric Youngdale
5 * Copyright 1995 Alexandre Julliard
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.
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.
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
22 #include "config.h"
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdarg.h>
29 #include "debugger.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "tlhelp32.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(winedbg);
37 /***********************************************************************
38 * print_help
40 * Implementation of the 'help' command.
42 void print_help(void)
44 int i = 0;
45 static const char * const helptext[] =
47 "The commands accepted by the Wine debugger are a reasonable",
48 "subset of the commands that gdb accepts.",
49 "The commands currently are:",
50 " help quit",
51 " break [*<addr>] watch *<addr>",
52 " delete break bpnum disable bpnum",
53 " enable bpnum condition <bpnum> [<expr>]",
54 " finish cont [N]",
55 " step [N] next [N]",
56 " stepi [N] nexti [N]",
57 " x <addr> print <expr>",
58 " display <expr> undisplay <disnum>",
59 " local display <expr> delete display <disnum>",
60 " enable display <disnum> disable display <disnum>",
61 " bt [<tid>|all] frame <n>",
62 " up down",
63 " list <lines> disassemble [<addr>][,<addr>]",
64 " show dir dir <path>",
65 " set <reg> = <expr> set *<addr> = <expr>",
66 " mode [16,32,vm86] pass",
67 " whatis info (see 'help info' for options)",
69 "The 'x' command accepts repeat counts and formats (including 'i') in the",
70 "same way that gdb does.\n",
72 "The following are examples of legal expressions:",
73 " $eax $eax+0x3 0x1000 ($eip + 256) *$eax *($esp + 3)",
74 " Also, a nm format symbol table can be read from a file using the",
75 " symbolfile command.", /* Symbols can also be defined individually with",
76 " the define command.", */
77 "",
78 NULL
81 while (helptext[i]) dbg_printf("%s\n", helptext[i++]);
85 /***********************************************************************
86 * info_help
88 * Implementation of the 'help info' command.
90 void info_help(void)
92 int i = 0;
93 static const char * const infotext[] =
95 "The info commands allow you to get assorted bits of interesting stuff",
96 "to be displayed. The options are:",
97 " info break Displays information about breakpoints",
98 " info class <name> Displays information about window class <name>",
99 " info display Shows auto-display expressions in use",
100 " info except <pid> Shows exception handler chain (in a given process)",
101 " info locals Displays values of all local vars for current frame",
102 " info maps <pid> Shows virtual mappings (in a given process)",
103 " info process Shows all running processes",
104 " info reg Displays values of the general registers at top of stack",
105 " info all-reg Displays the general and floating point registers",
106 " info segments <pid> Displays information about all known segments",
107 " info share Displays all loaded modules",
108 " info share <addr> Displays internal module state",
109 " info stack Dumps information about top of stack",
110 " info symbol <sym> Displays information about a given symbol",
111 " info thread Shows all running threads",
112 " info wnd <handle> Displays internal window state",
114 NULL
117 while (infotext[i]) dbg_printf("%s\n", infotext[i++]);
120 static const char* get_symtype_str(SYM_TYPE st)
122 switch (st)
124 case -1: return "\\";
125 default:
126 case SymNone: return "--none--";
127 case SymCoff: return "COFF";
128 case SymCv: return "CodeView";
129 case SymPdb: return "PDB";
130 case SymExport: return "Export";
131 case SymDeferred: return "Deferred";
132 case SymSym: return "Sym";
133 case SymDia: return "DIA";
134 case NumSymTypes: return "Stabs";
138 struct info_module
140 IMAGEHLP_MODULE* mi;
141 unsigned num_alloc;
142 unsigned num_used;
145 static void module_print_info(const IMAGEHLP_MODULE* mi, SYM_TYPE st)
147 dbg_printf("0x%08lx-%08lx\t%-16s%s\n",
148 mi->BaseOfImage, mi->BaseOfImage + mi->ImageSize,
149 get_symtype_str(st), mi->ModuleName);
152 static int module_compare(const void* p1, const void* p2)
154 return (char*)(((const IMAGEHLP_MODULE*)p1)->BaseOfImage) -
155 (char*)(((const IMAGEHLP_MODULE*)p2)->BaseOfImage);
158 static inline BOOL module_is_container(const IMAGEHLP_MODULE* wmod_cntnr,
159 const IMAGEHLP_MODULE* wmod_child)
161 return wmod_cntnr->BaseOfImage <= wmod_child->BaseOfImage &&
162 (DWORD)wmod_cntnr->BaseOfImage + wmod_cntnr->ImageSize >=
163 (DWORD)wmod_child->BaseOfImage + wmod_child->ImageSize;
166 static BOOL CALLBACK info_mod_cb(PSTR mod_name, DWORD base, void* ctx)
168 struct info_module* im = (struct info_module*)ctx;
170 if (im->num_used + 1 > im->num_alloc)
172 im->num_alloc += 16;
173 im->mi = dbg_heap_realloc(im->mi, im->num_alloc * sizeof(*im->mi));
175 im->mi[im->num_used].SizeOfStruct = sizeof(im->mi[im->num_used]);
176 if (SymGetModuleInfo(dbg_curr_process->handle, base, &im->mi[im->num_used]))
178 im->num_used++;
180 return TRUE;
183 /***********************************************************************
184 * info_win32_module
186 * Display information about a given module (DLL or EXE), or about all modules
188 void info_win32_module(DWORD base)
190 if (!dbg_curr_process || !dbg_curr_thread)
192 dbg_printf("Cannot get info on module while no process is loaded\n");
193 return;
196 if (base)
198 IMAGEHLP_MODULE mi;
200 mi.SizeOfStruct = sizeof(mi);
202 if (!SymGetModuleInfo(dbg_curr_process->handle, base, &mi))
204 dbg_printf("'0x%08lx' is not a valid module address\n", base);
205 return;
207 module_print_info(&mi, mi.SymType);
209 else
211 struct info_module im;
212 int i, j;
213 DWORD opt;
215 im.mi = NULL;
216 im.num_alloc = im.num_used = 0;
218 /* this is a wine specific options to return also ELF modules in the
219 * enumeration
221 SymSetOptions((opt = SymGetOptions()) | 0x40000000);
222 SymEnumerateModules(dbg_curr_process->handle, info_mod_cb, (void*)&im);
223 SymSetOptions(opt);
225 qsort(im.mi, im.num_used, sizeof(im.mi[0]), module_compare);
227 dbg_printf("Module\tAddress\t\t\tDebug info\tName (%d modules)\n", im.num_used);
229 for (i = 0; i < im.num_used; i++)
231 if (strstr(im.mi[i].ModuleName, "<elf>"))
233 dbg_printf("ELF\t");
234 module_print_info(&im.mi[i], (im.mi[i].SymType == SymDia) ? NumSymTypes : im.mi[i].SymType);
235 /* print all modules embedded in this one */
236 for (j = 0; j < im.num_used; j++)
238 if (!strstr(im.mi[j].ModuleName, "<elf>") && module_is_container(&im.mi[i], &im.mi[j]))
240 dbg_printf(" \\-PE\t");
241 module_print_info(&im.mi[j], -1);
245 else
247 /* check module is not embedded in another module */
248 for (j = 0; j < im.num_used; j++)
250 if (strstr(im.mi[j].ModuleName, "<elf>") && module_is_container(&im.mi[j], &im.mi[i]))
251 break;
253 if (j < im.num_used) continue;
254 if (strstr(im.mi[i].ModuleName, ".so") || strchr(im.mi[i].ModuleName, '<'))
255 dbg_printf("ELF\t");
256 else
257 dbg_printf("PE\t");
258 module_print_info(&im.mi[i], im.mi[i].SymType);
261 HeapFree(GetProcessHeap(), 0, im.mi);
265 struct class_walker
267 ATOM* table;
268 int used;
269 int alloc;
272 static void class_walker(HWND hWnd, struct class_walker* cw)
274 char clsName[128];
275 int i;
276 ATOM atom;
277 HWND child;
279 if (!GetClassName(hWnd, clsName, sizeof(clsName)))
280 return;
281 if ((atom = FindAtom(clsName)) == 0)
282 return;
284 for (i = 0; i < cw->used; i++)
286 if (cw->table[i] == atom)
287 break;
289 if (i == cw->used)
291 if (cw->used >= cw->alloc)
293 cw->alloc += 16;
294 cw->table = dbg_heap_realloc(cw->table, cw->alloc * sizeof(ATOM));
296 cw->table[cw->used++] = atom;
297 info_win32_class(hWnd, clsName);
301 if ((child = GetWindow(hWnd, GW_CHILD)) != 0)
302 class_walker(child, cw);
303 } while ((hWnd = GetWindow(hWnd, GW_HWNDNEXT)) != 0);
306 void info_win32_class(HWND hWnd, const char* name)
308 WNDCLASSEXA wca;
309 HINSTANCE hInst = hWnd ? (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE) : 0;
311 if (!name)
313 struct class_walker cw;
315 cw.table = NULL;
316 cw.used = cw.alloc = 0;
317 class_walker(GetDesktopWindow(), &cw);
318 HeapFree(GetProcessHeap(), 0, cw.table);
319 return;
322 if (!GetClassInfoEx(hInst, name, &wca))
324 dbg_printf("Cannot find class '%s'\n", name);
325 return;
328 dbg_printf("Class '%s':\n", name);
329 dbg_printf("style=0x%08x wndProc=0x%08lx\n"
330 "inst=%p icon=%p cursor=%p bkgnd=%p\n"
331 "clsExtra=%d winExtra=%d\n",
332 wca.style, (DWORD)wca.lpfnWndProc, wca.hInstance,
333 wca.hIcon, wca.hCursor, wca.hbrBackground,
334 wca.cbClsExtra, wca.cbWndExtra);
336 if (hWnd && wca.cbClsExtra)
338 int i;
339 WORD w;
341 dbg_printf("Extra bytes:");
342 for (i = 0; i < wca.cbClsExtra / 2; i++)
344 w = GetClassWord(hWnd, i * 2);
345 /* FIXME: depends on i386 endian-ity */
346 dbg_printf(" %02x %02x", HIBYTE(w), LOBYTE(w));
348 dbg_printf("\n");
350 dbg_printf("\n");
351 /* FIXME:
352 * + print #windows (or even list of windows...)
353 * + print extra bytes => this requires a window handle on this very class...
357 static void info_window(HWND hWnd, int indent)
359 char clsName[128];
360 char wndName[128];
361 HWND child;
365 if (!GetClassName(hWnd, clsName, sizeof(clsName)))
366 strcpy(clsName, "-- Unknown --");
367 if (!GetWindowText(hWnd, wndName, sizeof(wndName)))
368 strcpy(wndName, "-- Empty --");
370 dbg_printf("%*s%08x%*s %-17.17s %08lx %08lx %08lx %.14s\n",
371 indent, "", (UINT)hWnd, 12 - indent, "",
372 clsName, GetWindowLong(hWnd, GWL_STYLE),
373 GetWindowLongPtr(hWnd, GWLP_WNDPROC),
374 GetWindowThreadProcessId(hWnd, NULL), wndName);
376 if ((child = GetWindow(hWnd, GW_CHILD)) != 0)
377 info_window(child, indent + 1);
378 } while ((hWnd = GetWindow(hWnd, GW_HWNDNEXT)) != 0);
381 void info_win32_window(HWND hWnd, BOOL detailed)
383 char clsName[128];
384 char wndName[128];
385 RECT clientRect;
386 RECT windowRect;
387 int i;
388 WORD w;
390 if (!IsWindow(hWnd)) hWnd = GetDesktopWindow();
392 if (!detailed)
394 dbg_printf("%-20.20s %-17.17s %-8.8s %-8.8s %-8.8s %s\n",
395 "Window handle", "Class Name", "Style", "WndProc", "Thread", "Text");
396 info_window(hWnd, 0);
397 return;
400 if (!GetClassName(hWnd, clsName, sizeof(clsName)))
401 strcpy(clsName, "-- Unknown --");
402 if (!GetWindowText(hWnd, wndName, sizeof(wndName)))
403 strcpy(wndName, "-- Empty --");
404 if (!GetClientRect(hWnd, &clientRect) ||
405 !MapWindowPoints(hWnd, 0, (LPPOINT) &clientRect, 2))
406 SetRectEmpty(&clientRect);
407 if (!GetWindowRect(hWnd, &windowRect))
408 SetRectEmpty(&windowRect);
410 /* FIXME missing fields: hmemTaskQ, hrgnUpdate, dce, flags, pProp, scroll */
411 dbg_printf("next=%p child=%p parent=%p owner=%p class='%s'\n"
412 "inst=%p active=%p idmenu=%08lx\n"
413 "style=0x%08lx exstyle=0x%08lx wndproc=0x%08lx text='%s'\n"
414 "client=%ld,%ld-%ld,%ld window=%ld,%ld-%ld,%ld sysmenu=%p\n",
415 GetWindow(hWnd, GW_HWNDNEXT),
416 GetWindow(hWnd, GW_CHILD),
417 GetParent(hWnd),
418 GetWindow(hWnd, GW_OWNER),
419 clsName,
420 (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE),
421 GetLastActivePopup(hWnd),
422 GetWindowLongPtr(hWnd, GWLP_ID),
423 GetWindowLong(hWnd, GWL_STYLE),
424 GetWindowLong(hWnd, GWL_EXSTYLE),
425 GetWindowLongPtr(hWnd, GWLP_WNDPROC),
426 wndName,
427 clientRect.left, clientRect.top, clientRect.right, clientRect.bottom,
428 windowRect.left, windowRect.top, windowRect.right, windowRect.bottom,
429 GetSystemMenu(hWnd, FALSE));
431 if (GetClassLong(hWnd, GCL_CBWNDEXTRA))
433 dbg_printf("Extra bytes:");
434 for (i = 0; i < GetClassLong(hWnd, GCL_CBWNDEXTRA) / 2; i++)
436 w = GetWindowWord(hWnd, i * 2);
437 /* FIXME: depends on i386 endian-ity */
438 dbg_printf(" %02x %02x", HIBYTE(w), LOBYTE(w));
440 dbg_printf("\n");
442 dbg_printf("\n");
445 void info_win32_processes(void)
447 HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
448 if (snap != INVALID_HANDLE_VALUE)
450 PROCESSENTRY32 entry;
451 DWORD current = dbg_curr_process ? dbg_curr_process->pid : 0;
452 BOOL ok;
454 entry.dwSize = sizeof(entry);
455 ok = Process32First(snap, &entry);
457 dbg_printf(" %-8.8s %-8.8s %-8.8s %s (all id:s are in hex)\n",
458 "pid", "threads", "parent", "executable");
459 while (ok)
461 if (entry.th32ProcessID != GetCurrentProcessId())
462 dbg_printf("%c%08lx %-8ld %08lx '%s'\n",
463 (entry.th32ProcessID == current) ? '>' : ' ',
464 entry.th32ProcessID, entry.cntThreads,
465 entry.th32ParentProcessID, entry.szExeFile);
466 ok = Process32Next(snap, &entry);
468 CloseHandle(snap);
472 void info_win32_threads(void)
474 HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
475 if (snap != INVALID_HANDLE_VALUE)
477 THREADENTRY32 entry;
478 BOOL ok;
479 DWORD lastProcessId = 0;
481 entry.dwSize = sizeof(entry);
482 ok = Thread32First(snap, &entry);
484 dbg_printf("%-8.8s %-8.8s %s (all id:s are in hex)\n",
485 "process", "tid", "prio");
486 while (ok)
488 if (entry.th32OwnerProcessID != GetCurrentProcessId())
490 /* FIXME: this assumes that, in the snapshot, all threads of a same process are
491 * listed sequentially, which is not specified in the doc (Wine's implementation
492 * does it)
494 if (entry.th32OwnerProcessID != lastProcessId)
496 struct dbg_process* p = dbg_get_process(entry.th32OwnerProcessID);
498 dbg_printf("%08lx%s %s\n",
499 entry.th32OwnerProcessID, p ? " (D)" : "", p ? p->imageName : "");
500 lastProcessId = entry.th32OwnerProcessID;
502 dbg_printf("\t%08lx %4ld%s\n",
503 entry.th32ThreadID, entry.tpBasePri,
504 (entry.th32ThreadID == dbg_curr_tid) ? " <==" : "");
507 ok = Thread32Next(snap, &entry);
510 CloseHandle(snap);
514 /***********************************************************************
515 * info_win32_exceptions
517 * Get info on the exception frames of a given thread.
519 void info_win32_exceptions(DWORD tid)
521 struct dbg_thread* thread;
522 void* next_frame;
524 if (!dbg_curr_process || !dbg_curr_thread)
526 dbg_printf("Cannot get info on exceptions while no process is loaded\n");
527 return;
530 dbg_printf("Exception frames:\n");
532 if (tid == dbg_curr_tid) thread = dbg_curr_thread;
533 else
535 thread = dbg_get_thread(dbg_curr_process, tid);
537 if (!thread)
539 dbg_printf("Unknown thread id (0x%08lx) in current process\n", tid);
540 return;
542 if (SuspendThread(thread->handle) == -1)
544 dbg_printf("Can't suspend thread id (0x%08lx)\n", tid);
545 return;
549 if (!dbg_read_memory(thread->teb, &next_frame, sizeof(next_frame)))
551 dbg_printf("Can't read TEB:except_frame\n");
552 return;
555 while (next_frame != (void*)-1)
557 EXCEPTION_REGISTRATION_RECORD frame;
559 dbg_printf("%p: ", next_frame);
560 if (!dbg_read_memory(next_frame, &frame, sizeof(frame)))
562 dbg_printf("Invalid frame address\n");
563 break;
565 dbg_printf("prev=%p handler=%p\n", frame.Prev, frame.Handler);
566 next_frame = frame.Prev;
569 if (tid != dbg_curr_tid) ResumeThread(thread->handle);
572 void info_win32_segments(DWORD start, int length)
574 char flags[3];
575 DWORD i;
576 LDT_ENTRY le;
578 if (length == -1) length = (8192 - start);
580 for (i = start; i < start + length; i++)
582 if (!GetThreadSelectorEntry(dbg_curr_thread->handle, (i << 3) | 7, &le))
583 continue;
585 if (le.HighWord.Bits.Type & 0x08)
587 flags[0] = (le.HighWord.Bits.Type & 0x2) ? 'r' : '-';
588 flags[1] = '-';
589 flags[2] = 'x';
591 else
593 flags[0] = 'r';
594 flags[1] = (le.HighWord.Bits.Type & 0x2) ? 'w' : '-';
595 flags[2] = '-';
597 dbg_printf("%04lx: sel=%04lx base=%08x limit=%08x %d-bit %c%c%c\n",
598 i, (i << 3) | 7,
599 (le.HighWord.Bits.BaseHi << 24) +
600 (le.HighWord.Bits.BaseMid << 16) + le.BaseLow,
601 ((le.HighWord.Bits.LimitHi << 8) + le.LimitLow) <<
602 (le.HighWord.Bits.Granularity ? 12 : 0),
603 le.HighWord.Bits.Default_Big ? 32 : 16,
604 flags[0], flags[1], flags[2]);
608 void info_win32_virtual(DWORD pid)
610 MEMORY_BASIC_INFORMATION mbi;
611 char* addr = 0;
612 const char* state;
613 const char* type;
614 char prot[3+1];
615 HANDLE hProc;
617 if (pid == dbg_curr_pid)
619 if (dbg_curr_process == NULL)
621 dbg_printf("Cannot look at mapping of current process, while no process is loaded\n");
622 return;
624 hProc = dbg_curr_process->handle;
626 else
628 hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
629 if (hProc == NULL)
631 dbg_printf("Cannot open process <%lu>\n", pid);
632 return;
636 dbg_printf("Address Size State Type RWX\n");
638 while (VirtualQueryEx(hProc, addr, &mbi, sizeof(mbi)) >= sizeof(mbi))
640 switch (mbi.State)
642 case MEM_COMMIT: state = "commit "; break;
643 case MEM_FREE: state = "free "; break;
644 case MEM_RESERVE: state = "reserve"; break;
645 default: state = "??? "; break;
647 if (mbi.State != MEM_FREE)
649 switch (mbi.Type)
651 case MEM_IMAGE: type = "image "; break;
652 case MEM_MAPPED: type = "mapped "; break;
653 case MEM_PRIVATE: type = "private"; break;
654 case 0: type = " "; break;
655 default: type = "??? "; break;
657 memset(prot, ' ' , sizeof(prot) - 1);
658 prot[sizeof(prot) - 1] = '\0';
659 if (mbi.AllocationProtect & (PAGE_READONLY|PAGE_READWRITE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE))
660 prot[0] = 'R';
661 if (mbi.AllocationProtect & (PAGE_READWRITE|PAGE_EXECUTE_READWRITE))
662 prot[1] = 'W';
663 if (mbi.AllocationProtect & (PAGE_WRITECOPY|PAGE_EXECUTE_WRITECOPY))
664 prot[1] = 'C';
665 if (mbi.AllocationProtect & (PAGE_EXECUTE|PAGE_EXECUTE_READ|PAGE_EXECUTE_READWRITE))
666 prot[2] = 'X';
668 else
670 type = "";
671 prot[0] = '\0';
673 dbg_printf("%08lx %08lx %s %s %s\n",
674 (DWORD)addr, (DWORD)addr + mbi.RegionSize - 1, state, type, prot);
675 if (addr + mbi.RegionSize < addr) /* wrap around ? */
676 break;
677 addr += mbi.RegionSize;
679 if (pid != dbg_curr_pid) CloseHandle(hProc);
682 void info_wine_dbg_channel(BOOL turn_on, const char* cls, const char* name)
684 struct dbg_lvalue lvalue;
685 struct __wine_debug_channel channel;
686 unsigned char mask;
687 int done = 0;
688 BOOL bAll;
689 void* addr;
691 if (!dbg_curr_process || !dbg_curr_thread)
693 dbg_printf("Cannot set/get debug channels while no process is loaded\n");
694 return;
697 if (symbol_get_lvalue("debug_options", -1, &lvalue, FALSE) != sglv_found)
699 return;
701 addr = memory_to_linear_addr(&lvalue.addr);
703 if (!cls) mask = ~0;
704 else if (!strcmp(cls, "fixme")) mask = (1 << __WINE_DBCL_FIXME);
705 else if (!strcmp(cls, "err")) mask = (1 << __WINE_DBCL_ERR);
706 else if (!strcmp(cls, "warn")) mask = (1 << __WINE_DBCL_WARN);
707 else if (!strcmp(cls, "trace")) mask = (1 << __WINE_DBCL_TRACE);
708 else
710 dbg_printf("Unknown debug class %s\n", cls);
711 return;
714 bAll = !strcmp("all", name);
715 while (addr && dbg_read_memory(addr, &channel, sizeof(channel)))
717 if (!channel.name[0]) break;
718 if (bAll || !strcmp( channel.name, name ))
720 if (turn_on) channel.flags |= mask;
721 else channel.flags &= ~mask;
722 if (dbg_write_memory(addr, &channel, sizeof(channel))) done++;
724 addr = (struct __wine_debug_channel *)addr + 1;
726 if (!done) dbg_printf("Unable to find debug channel %s\n", name);
727 else WINE_TRACE("Changed %d channel instances\n", done);