1/**
2 * Copyright (c) 2021 Expo, Inc.
3 * Copyright (c) 2018 Drifty Co.
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8import Debug from 'debug';
9import { Socket } from 'net';
10import * as path from 'path';
11
12import { GDBProtocolClient } from '../protocol/GDBProtocol';
13import { ServiceClient } from './ServiceClient';
14
15const debug = Debug('expo:apple-device:client:debugserver');
16
17export class DebugserverClient extends ServiceClient<GDBProtocolClient> {
18  constructor(public socket: Socket) {
19    super(socket, new GDBProtocolClient(socket));
20  }
21
22  async setMaxPacketSize(size: number) {
23    return this.sendCommand('QSetMaxPacketSize:', [size.toString()]);
24  }
25
26  async setWorkingDir(workingDir: string) {
27    return this.sendCommand('QSetWorkingDir:', [workingDir]);
28  }
29
30  async checkLaunchSuccess() {
31    return this.sendCommand('qLaunchSuccess', []);
32  }
33
34  async attachByName(name: string) {
35    const hexName = Buffer.from(name).toString('hex');
36    return this.sendCommand(`vAttachName;${hexName}`, []);
37  }
38
39  async continue() {
40    return this.sendCommand('c', []);
41  }
42
43  halt() {
44    // ^C
45    debug('Sending ^C to debugserver');
46    return this.protocolClient.socket.write('\u0003');
47  }
48
49  async kill() {
50    debug(`kill`);
51    const msg: any = { cmd: 'k', args: [] };
52    return this.protocolClient.sendMessage(msg, (resp: string, resolve: any) => {
53      debug(`kill:response: ${resp}`);
54      this.protocolClient.socket.write('+');
55      const parts = resp.split(';');
56      for (const part of parts) {
57        if (part.includes('description')) {
58          // description:{hex encoded message like: "Terminated with signal 9"}
59          resolve(Buffer.from(part.split(':')[1], 'hex').toString('ascii'));
60        }
61      }
62    });
63  }
64
65  // TODO support app args
66  // https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets
67  // A arglen,argnum,arg,
68  async launchApp(appPath: string, executableName: string) {
69    const fullPath = path.join(appPath, executableName);
70    const hexAppPath = Buffer.from(fullPath).toString('hex');
71    const appCommand = `A${hexAppPath.length},0,${hexAppPath}`;
72    return this.sendCommand(appCommand, []);
73  }
74
75  async sendCommand(cmd: string, args: string[]) {
76    const msg = { cmd, args };
77    debug(`Sending command: ${cmd}, args: ${args}`);
78    const resp = await this.protocolClient.sendMessage(msg);
79    // we need to ACK as well
80    this.protocolClient.socket.write('+');
81    return resp;
82  }
83}
84