src/debugger/gdb-stub.c (view raw)
1/* Copyright (c) 2013-2014 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "gdb-stub.h"
7
8#include "arm/isa-inlines.h"
9#include "core/core.h"
10#include "gba/memory.h"
11
12#include <signal.h>
13
14#ifndef SIGTRAP
15#define SIGTRAP 5 /* Win32 Signals do not include SIGTRAP */
16#endif
17
18#define SOCKET_TIMEOUT 50
19
20enum GDBError {
21 GDB_NO_ERROR = 0x00,
22 GDB_BAD_ARGUMENTS = 0x06,
23 GDB_UNSUPPORTED_COMMAND = 0x07
24};
25
26enum {
27 MACH_O_ARM = 12,
28 MACH_O_ARM_V4T = 5
29};
30
31static void _sendMessage(struct GDBStub* stub);
32
33static void _gdbStubDeinit(struct mDebugger* debugger) {
34 struct GDBStub* stub = (struct GDBStub*) debugger;
35 if (!SOCKET_FAILED(stub->socket)) {
36 GDBStubShutdown(stub);
37 }
38}
39
40static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {
41 struct GDBStub* stub = (struct GDBStub*) debugger;
42 switch (reason) {
43 case DEBUGGER_ENTER_MANUAL:
44 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
45 break;
46 case DEBUGGER_ENTER_BREAKPOINT:
47 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); // TODO: Use hwbreak/swbreak if gdb supports it
48 break;
49 case DEBUGGER_ENTER_WATCHPOINT:
50 if (info) {
51 const char* type = 0;
52 switch (info->watchType) {
53 case WATCHPOINT_WRITE:
54 if (info->newValue == info->oldValue) {
55 if (stub->d.state == DEBUGGER_PAUSED) {
56 stub->d.state = DEBUGGER_RUNNING;
57 }
58 return;
59 }
60 type = "watch";
61 break;
62 case WATCHPOINT_READ:
63 type = "rwatch";
64 break;
65 case WATCHPOINT_RW:
66 type = "awatch";
67 break;
68 }
69 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%s:%08x;", SIGTRAP, type, info->address);
70 } else {
71 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
72 }
73 break;
74 case DEBUGGER_ENTER_ILLEGAL_OP:
75 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL);
76 break;
77 case DEBUGGER_ENTER_ATTACHED:
78 return;
79 }
80 _sendMessage(stub);
81}
82
83static void _gdbStubPoll(struct mDebugger* debugger) {
84 struct GDBStub* stub = (struct GDBStub*) debugger;
85 --stub->untilPoll;
86 if (stub->untilPoll > 0) {
87 return;
88 }
89 stub->untilPoll = GDB_STUB_INTERVAL;
90 stub->shouldBlock = false;
91 GDBStubUpdate(stub);
92}
93
94static void _gdbStubWait(struct mDebugger* debugger) {
95 struct GDBStub* stub = (struct GDBStub*) debugger;
96 stub->shouldBlock = true;
97 GDBStubUpdate(stub);
98}
99
100static void _ack(struct GDBStub* stub) {
101 char ack = '+';
102 SocketSend(stub->connection, &ack, 1);
103}
104
105static void _nak(struct GDBStub* stub) {
106 char nak = '-';
107 mLOG(DEBUGGER, WARN, "Packet error");
108 SocketSend(stub->connection, &nak, 1);
109}
110
111static uint32_t _hex2int(const char* hex, int maxDigits) {
112 uint32_t value = 0;
113 uint8_t letter;
114
115 while (maxDigits--) {
116 letter = *hex - '0';
117 if (letter > 9) {
118 letter = *hex - 'a';
119 if (letter > 5) {
120 break;
121 }
122 value *= 0x10;
123 value += letter + 10;
124 } else {
125 value *= 0x10;
126 value += letter;
127 }
128 ++hex;
129 }
130 return value;
131}
132
133static void _int2hex8(uint8_t value, char* out) {
134 static const char language[] = "0123456789abcdef";
135 out[0] = language[value >> 4];
136 out[1] = language[value & 0xF];
137}
138
139static void _int2hex32(uint32_t value, char* out) {
140 static const char language[] = "0123456789abcdef";
141 out[6] = language[value >> 28];
142 out[7] = language[(value >> 24) & 0xF];
143 out[4] = language[(value >> 20) & 0xF];
144 out[5] = language[(value >> 16) & 0xF];
145 out[2] = language[(value >> 12) & 0xF];
146 out[3] = language[(value >> 8) & 0xF];
147 out[0] = language[(value >> 4) & 0xF];
148 out[1] = language[value & 0xF];
149}
150
151static uint32_t _readHex(const char* in, unsigned* out) {
152 unsigned i;
153 for (i = 0; i < 8; ++i) {
154 if (in[i] == ',' || in[i] == ':' || in[i] == '=') {
155 break;
156 }
157 }
158 *out += i;
159 return _hex2int(in, i);
160}
161
162static void _sendMessage(struct GDBStub* stub) {
163 if (stub->lineAck != GDB_ACK_OFF) {
164 stub->lineAck = GDB_ACK_PENDING;
165 }
166 uint8_t checksum = 0;
167 int i = 1;
168 char buffer = stub->outgoing[0];
169 char swap;
170 stub->outgoing[0] = '$';
171 if (buffer) {
172 for (; i < GDB_STUB_MAX_LINE - 5; ++i) {
173 checksum += buffer;
174 swap = stub->outgoing[i];
175 stub->outgoing[i] = buffer;
176 buffer = swap;
177 if (!buffer) {
178 ++i;
179 break;
180 }
181 }
182 }
183 stub->outgoing[i] = '#';
184 _int2hex8(checksum, &stub->outgoing[i + 1]);
185 stub->outgoing[i + 3] = 0;
186 mLOG(DEBUGGER, DEBUG, "> %s", stub->outgoing);
187 SocketSend(stub->connection, stub->outgoing, i + 3);
188}
189
190static void _error(struct GDBStub* stub, enum GDBError error) {
191 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "E%02x", error);
192 _sendMessage(stub);
193}
194
195static void _writeHostInfo(struct GDBStub* stub) {
196 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "cputype:%u;cpusubtype:%u:ostype:none;vendor:none;endian:little;ptrsize:4;", MACH_O_ARM, MACH_O_ARM_V4T);
197 _sendMessage(stub);
198}
199
200static void _continue(struct GDBStub* stub, const char* message) {
201 stub->d.state = DEBUGGER_CUSTOM;
202 stub->untilPoll = GDB_STUB_INTERVAL;
203 // TODO: parse message
204 UNUSED(message);
205}
206
207static void _step(struct GDBStub* stub, const char* message) {
208 stub->d.core->step(stub->d.core);
209 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP);
210 _sendMessage(stub);
211 // TODO: parse message
212 UNUSED(message);
213}
214
215static void _writeMemoryBinary(struct GDBStub* stub, const char* message) {
216 const char* readAddress = message;
217 unsigned i = 0;
218 uint32_t address = _readHex(readAddress, &i);
219 readAddress += i + 1;
220
221 i = 0;
222 uint32_t size = _readHex(readAddress, &i);
223 readAddress += i + 1;
224
225 if (size > 512) {
226 _error(stub, GDB_BAD_ARGUMENTS);
227 return;
228 }
229
230 struct ARMCore* cpu = stub->d.core->cpu;
231 for (i = 0; i < size; i++) {
232 uint8_t byte = *readAddress;
233 ++readAddress;
234
235 // Parse escape char
236 if (byte == 0x7D) {
237 byte = *readAddress ^ 0x20;
238 ++readAddress;
239 }
240
241 GBAPatch8(cpu, address + i, byte, 0);
242 }
243
244 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
245 _sendMessage(stub);
246}
247
248
249static void _writeMemory(struct GDBStub* stub, const char* message) {
250 const char* readAddress = message;
251 unsigned i = 0;
252 uint32_t address = _readHex(readAddress, &i);
253 readAddress += i + 1;
254
255 i = 0;
256 uint32_t size = _readHex(readAddress, &i);
257 readAddress += i + 1;
258
259 if (size > 512) {
260 _error(stub, GDB_BAD_ARGUMENTS);
261 return;
262 }
263
264 struct ARMCore* cpu = stub->d.core->cpu;
265 for (i = 0; i < size; ++i, readAddress += 2) {
266 uint8_t byte = _hex2int(readAddress, 2);
267 GBAPatch8(cpu, address + i, byte, 0);
268 }
269
270 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
271 _sendMessage(stub);
272}
273
274static void _readMemory(struct GDBStub* stub, const char* message) {
275 const char* readAddress = message;
276 unsigned i = 0;
277 uint32_t address = _readHex(readAddress, &i);
278 readAddress += i + 1;
279 uint32_t size = _readHex(readAddress, &i);
280 if (size > 512) {
281 _error(stub, GDB_BAD_ARGUMENTS);
282 return;
283 }
284 struct ARMCore* cpu = stub->d.core->cpu;
285 int writeAddress = 0;
286 for (i = 0; i < size; ++i, writeAddress += 2) {
287 uint8_t byte = cpu->memory.load8(cpu, address + i, 0);
288 _int2hex8(byte, &stub->outgoing[writeAddress]);
289 }
290 stub->outgoing[writeAddress] = 0;
291 _sendMessage(stub);
292}
293
294static void _writeGPRs(struct GDBStub* stub, const char* message) {
295 struct ARMCore* cpu = stub->d.core->cpu;
296 const char* readAddress = message;
297
298 int r;
299 for (r = 0; r <= ARM_PC; ++r) {
300 cpu->gprs[r] = _hex2int(readAddress, 8);
301 readAddress += 8;
302 }
303 int32_t currentCycles = 0;
304 if (cpu->executionMode == MODE_ARM) {
305 ARM_WRITE_PC;
306 } else {
307 THUMB_WRITE_PC;
308 }
309
310 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
311 _sendMessage(stub);
312}
313
314static void _readGPRs(struct GDBStub* stub, const char* message) {
315 struct ARMCore* cpu = stub->d.core->cpu;
316 UNUSED(message);
317 int r;
318 int i = 0;
319 for (r = 0; r < ARM_PC; ++r) {
320 _int2hex32(cpu->gprs[r], &stub->outgoing[i]);
321 i += 8;
322 }
323 _int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]);
324 i += 8;
325
326 stub->outgoing[i] = 0;
327 _sendMessage(stub);
328}
329
330static void _writeRegister(struct GDBStub* stub, const char* message) {
331 struct ARMCore* cpu = stub->d.core->cpu;
332 const char* readAddress = message;
333
334 unsigned i = 0;
335 uint32_t reg = _readHex(readAddress, &i);
336 readAddress += i + 1;
337
338 uint32_t value = _readHex(readAddress, &i);
339
340#ifdef _MSC_VER
341 value = _byteswap_ulong(value);
342#else
343 value = __builtin_bswap32(value);
344#endif
345
346 if (reg <= ARM_PC) {
347 cpu->gprs[reg] = value;
348 if (reg == ARM_PC) {
349 int32_t currentCycles = 0;
350 if (cpu->executionMode == MODE_ARM) {
351 ARM_WRITE_PC;
352 } else {
353 THUMB_WRITE_PC;
354 }
355 }
356 } else if (reg == 0x19) {
357 cpu->cpsr.packed = value;
358 } else {
359 stub->outgoing[0] = '\0';
360 _sendMessage(stub);
361 return;
362 }
363
364 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
365 _sendMessage(stub);
366}
367
368static void _readRegister(struct GDBStub* stub, const char* message) {
369 struct ARMCore* cpu = stub->d.core->cpu;
370 const char* readAddress = message;
371 unsigned i = 0;
372 uint32_t reg = _readHex(readAddress, &i);
373 uint32_t value;
374 if (reg < 0x10) {
375 value = cpu->gprs[reg];
376 } else if (reg == 0x19) {
377 value = cpu->cpsr.packed;
378 } else {
379 stub->outgoing[0] = '\0';
380 _sendMessage(stub);
381 return;
382 }
383 _int2hex32(value, stub->outgoing);
384 stub->outgoing[8] = '\0';
385 _sendMessage(stub);
386}
387
388static void _processQReadCommand(struct GDBStub* stub, const char* message) {
389 stub->outgoing[0] = '\0';
390 if (!strncmp("HostInfo#", message, 9)) {
391 _writeHostInfo(stub);
392 return;
393 }
394 if (!strncmp("Attached#", message, 9)) {
395 strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
396 } else if (!strncmp("VAttachOrWaitSupported#", message, 23)) {
397 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
398 } else if (!strncmp("C#", message, 2)) {
399 strncpy(stub->outgoing, "QC1", GDB_STUB_MAX_LINE - 4);
400 } else if (!strncmp("fThreadInfo#", message, 12)) {
401 strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4);
402 } else if (!strncmp("sThreadInfo#", message, 12)) {
403 strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4);
404 }
405 _sendMessage(stub);
406}
407
408static void _processQWriteCommand(struct GDBStub* stub, const char* message) {
409 stub->outgoing[0] = '\0';
410 if (!strncmp("StartNoAckMode#", message, 16)) {
411 stub->lineAck = GDB_ACK_OFF;
412 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
413 }
414 _sendMessage(stub);
415}
416
417static void _processVWriteCommand(struct GDBStub* stub, const char* message) {
418 UNUSED(message);
419 stub->outgoing[0] = '\0';
420 _sendMessage(stub);
421}
422
423static void _processVReadCommand(struct GDBStub* stub, const char* message) {
424 stub->outgoing[0] = '\0';
425 if (!strncmp("Attach", message, 6)) {
426 strncpy(stub->outgoing, "1", GDB_STUB_MAX_LINE - 4);
427 mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
428 }
429 _sendMessage(stub);
430}
431
432static void _setBreakpoint(struct GDBStub* stub, const char* message) {
433 const char* readAddress = &message[2];
434 unsigned i = 0;
435 uint32_t address = _readHex(readAddress, &i);
436 readAddress += i + 1;
437 uint32_t kind = _readHex(readAddress, &i); // We don't use this in hardware watchpoints
438 UNUSED(kind);
439
440 switch (message[0]) {
441 case '0': // Memory breakpoints are not currently supported
442 case '1':
443 stub->d.platform->setBreakpoint(stub->d.platform, address);
444 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
445 _sendMessage(stub);
446 break;
447 case '2':
448 stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_WRITE);
449 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
450 _sendMessage(stub);
451 break;
452 case '3':
453 stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_READ);
454 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
455 _sendMessage(stub);
456 break;
457 case '4':
458 stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_RW);
459 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
460 _sendMessage(stub);
461 break;
462 default:
463 stub->outgoing[0] = '\0';
464 _sendMessage(stub);
465 break;
466 }
467}
468
469static void _clearBreakpoint(struct GDBStub* stub, const char* message) {
470 const char* readAddress = &message[2];
471 unsigned i = 0;
472 uint32_t address = _readHex(readAddress, &i);
473 switch (message[0]) {
474 case '0': // Memory breakpoints are not currently supported
475 case '1':
476 stub->d.platform->clearBreakpoint(stub->d.platform, address);
477 break;
478 case '2':
479 case '3':
480 case '4':
481 stub->d.platform->clearWatchpoint(stub->d.platform, address);
482 break;
483 default:
484 break;
485 }
486 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
487 _sendMessage(stub);
488}
489
490size_t _parseGDBMessage(struct GDBStub* stub, const char* message) {
491 uint8_t checksum = 0;
492 int parsed = 1;
493 switch (*message) {
494 case '+':
495 stub->lineAck = GDB_ACK_RECEIVED;
496 return parsed;
497 case '-':
498 stub->lineAck = GDB_NAK_RECEIVED;
499 return parsed;
500 case '$':
501 ++message;
502 break;
503 case '\x03':
504 mDebuggerEnter(&stub->d, DEBUGGER_ENTER_MANUAL, 0);
505 return parsed;
506 default:
507 _nak(stub);
508 return parsed;
509 }
510
511 int i;
512 char messageType = message[0];
513 for (i = 0; message[i] != '#'; ++i, ++parsed) {
514 checksum += message[i];
515 }
516 if (!message[i]) {
517 _nak(stub);
518 return parsed;
519 }
520 ++i;
521 ++parsed;
522 if (!message[i]) {
523 _nak(stub);
524 return parsed;
525 } else if (!message[i + 1]) {
526 ++parsed;
527 _nak(stub);
528 return parsed;
529 }
530 parsed += 2;
531 int networkChecksum = _hex2int(&message[i], 2);
532 if (networkChecksum != checksum) {
533 mLOG(DEBUGGER, WARN, "Checksum error: expected %02x, got %02x", checksum, networkChecksum);
534 _nak(stub);
535 return parsed;
536 }
537
538 _ack(stub);
539 ++message;
540 switch (messageType) {
541 case '?':
542 snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT);
543 _sendMessage(stub);
544 break;
545 case 'c':
546 _continue(stub, message);
547 break;
548 case 'G':
549 _writeGPRs(stub, message);
550 break;
551 case 'g':
552 _readGPRs(stub, message);
553 break;
554 case 'H':
555 // This is faked because we only have one thread
556 strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
557 _sendMessage(stub);
558 break;
559 case 'M':
560 _writeMemory(stub, message);
561 break;
562 case 'm':
563 _readMemory(stub, message);
564 break;
565 case 'P':
566 _writeRegister(stub, message);
567 break;
568 case 'p':
569 _readRegister(stub, message);
570 break;
571 case 'Q':
572 _processQWriteCommand(stub, message);
573 break;
574 case 'q':
575 _processQReadCommand(stub, message);
576 break;
577 case 's':
578 _step(stub, message);
579 break;
580 case 'V':
581 _processVWriteCommand(stub, message);
582 break;
583 case 'v':
584 _processVReadCommand(stub, message);
585 break;
586 case 'X':
587 _writeMemoryBinary(stub, message);
588 break;
589 case 'Z':
590 _setBreakpoint(stub, message);
591 break;
592 case 'z':
593 _clearBreakpoint(stub, message);
594 break;
595 default:
596 _error(stub, GDB_UNSUPPORTED_COMMAND);
597 break;
598 }
599 return parsed;
600}
601
602void GDBStubCreate(struct GDBStub* stub) {
603 stub->socket = INVALID_SOCKET;
604 stub->connection = INVALID_SOCKET;
605 stub->d.init = 0;
606 stub->d.deinit = _gdbStubDeinit;
607 stub->d.paused = _gdbStubWait;
608 stub->d.entered = _gdbStubEntered;
609 stub->d.custom = _gdbStubPoll;
610 stub->untilPoll = GDB_STUB_INTERVAL;
611 stub->lineAck = GDB_ACK_PENDING;
612 stub->shouldBlock = false;
613}
614
615bool GDBStubListen(struct GDBStub* stub, int port, const struct Address* bindAddress) {
616 if (!SOCKET_FAILED(stub->socket)) {
617 GDBStubShutdown(stub);
618 }
619 stub->socket = SocketOpenTCP(port, bindAddress);
620 if (SOCKET_FAILED(stub->socket)) {
621 mLOG(DEBUGGER, ERROR, "Couldn't open socket");
622 return false;
623 }
624 if (!SocketSetBlocking(stub->socket, false)) {
625 goto cleanup;
626 }
627 int err = SocketListen(stub->socket, 1);
628 if (err) {
629 goto cleanup;
630 }
631
632 return true;
633
634cleanup:
635 mLOG(DEBUGGER, ERROR, "Couldn't listen on port");
636 SocketClose(stub->socket);
637 stub->socket = INVALID_SOCKET;
638 return false;
639}
640
641void GDBStubHangup(struct GDBStub* stub) {
642 if (!SOCKET_FAILED(stub->connection)) {
643 SocketClose(stub->connection);
644 stub->connection = INVALID_SOCKET;
645 }
646 if (stub->d.state == DEBUGGER_PAUSED) {
647 stub->d.state = DEBUGGER_RUNNING;
648 }
649}
650
651void GDBStubShutdown(struct GDBStub* stub) {
652 GDBStubHangup(stub);
653 if (!SOCKET_FAILED(stub->socket)) {
654 SocketClose(stub->socket);
655 stub->socket = INVALID_SOCKET;
656 }
657}
658
659void GDBStubUpdate(struct GDBStub* stub) {
660 if (stub->socket == INVALID_SOCKET) {
661 if (stub->d.state == DEBUGGER_PAUSED) {
662 stub->d.state = DEBUGGER_RUNNING;
663 }
664 return;
665 }
666 if (stub->connection == INVALID_SOCKET) {
667 if (stub->shouldBlock) {
668 Socket reads = stub->socket;
669 SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT);
670 }
671 stub->connection = SocketAccept(stub->socket, 0);
672 if (!SOCKET_FAILED(stub->connection)) {
673 if (!SocketSetBlocking(stub->connection, false)) {
674 goto connectionLost;
675 }
676 mDebuggerEnter(&stub->d, DEBUGGER_ENTER_ATTACHED, 0);
677 } else if (SocketWouldBlock()) {
678 return;
679 } else {
680 goto connectionLost;
681 }
682 }
683 while (true) {
684 if (stub->shouldBlock) {
685 Socket reads = stub->connection;
686 SocketPoll(1, &reads, 0, 0, SOCKET_TIMEOUT);
687 }
688 ssize_t messageLen = SocketRecv(stub->connection, stub->line, GDB_STUB_MAX_LINE - 1);
689 if (messageLen == 0) {
690 goto connectionLost;
691 }
692 if (messageLen == -1) {
693 if (SocketWouldBlock()) {
694 return;
695 }
696 goto connectionLost;
697 }
698 stub->line[messageLen] = '\0';
699 mLOG(DEBUGGER, DEBUG, "< %s", stub->line);
700 ssize_t position = 0;
701 while (position < messageLen) {
702 position += _parseGDBMessage(stub, &stub->line[position]);
703 }
704 }
705
706connectionLost:
707 mLOG(DEBUGGER, WARN, "Connection lost");
708 GDBStubHangup(stub);
709}