| 10 | const COMMAND_SENTINEL_REGEX = new RegExp(`(^|\\r?\\n)${COMMAND_SENTINEL}(\\r?\\n)`); |
| 11 | |
| 12 | class LldbCliBackend implements DebuggerBackend { |
| 13 | readonly kind = 'lldb-cli' as const; |
| 14 | |
| 15 | private readonly spawner: InteractiveSpawner; |
| 16 | private readonly prompt = LLDB_PROMPT; |
| 17 | private readonly process: InteractiveProcess; |
| 18 | private buffer = ''; |
| 19 | private pending: { |
| 20 | resolve: (output: string) => void; |
| 21 | reject: (error: Error) => void; |
| 22 | timeout: NodeJS.Timeout; |
| 23 | } | null = null; |
| 24 | private queue: Promise<unknown> = Promise.resolve(); |
| 25 | private ready: Promise<void>; |
| 26 | private disposed = false; |
| 27 | |
| 28 | constructor(spawner: InteractiveSpawner) { |
| 29 | this.spawner = spawner; |
| 30 | const lldbCommand = [ |
| 31 | 'xcrun', |
| 32 | 'lldb', |
| 33 | '--no-lldbinit', |
| 34 | '-o', |
| 35 | `settings set prompt "${this.prompt}"`, |
| 36 | ]; |
| 37 | |
| 38 | this.process = this.spawner(lldbCommand); |
| 39 | |
| 40 | this.process.process.stdout?.on('data', (data: Buffer) => this.handleData(data)); |
| 41 | this.process.process.stderr?.on('data', (data: Buffer) => this.handleData(data)); |
| 42 | this.process.process.on('exit', (code, signal) => { |
| 43 | const detail = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`; |
| 44 | this.failPending(new Error(`LLDB process exited (${detail})`)); |
| 45 | }); |
| 46 | |
| 47 | this.ready = this.initialize(); |
| 48 | } |
| 49 | |
| 50 | private async initialize(): Promise<void> { |
| 51 | // Prime the prompt by running a sentinel command we can parse reliably. |
| 52 | this.process.write(`script print("${COMMAND_SENTINEL}")\n`); |
| 53 | await this.waitForSentinel(DEFAULT_STARTUP_TIMEOUT_MS); |
| 54 | } |
| 55 | |
| 56 | async waitUntilReady(): Promise<void> { |
| 57 | await this.ready; |
| 58 | } |
| 59 | |
| 60 | async attach(opts: { pid: number; simulatorId: string; waitFor?: boolean }): Promise<void> { |
| 61 | const command = opts.waitFor |
| 62 | ? `process attach --pid ${opts.pid} --waitfor` |
| 63 | : `process attach --pid ${opts.pid}`; |
| 64 | const output = await this.runCommand(command); |
| 65 | assertNoLldbError('attach', output); |
| 66 | } |
| 67 | |
| 68 | async detach(): Promise<void> { |
| 69 | const output = await this.runCommand('process detach'); |