(thread, scopeModule)
| 264 | } |
| 265 | |
| 266 | function startStalk(thread, scopeModule) { |
| 267 | const tid = thread.id; |
| 268 | if (stalked.has(tid)) return; |
| 269 | if (stalkedCount >= CONFIG.maxThreads) { |
| 270 | console.log(`${C.yellow}[stalk] skip ${tid} (maxThreads ${CONFIG.maxThreads} reached)${C.reset}`); |
| 271 | return; |
| 272 | } |
| 273 | |
| 274 | const state = { |
| 275 | name: thread.name || `(tid ${tid})`, |
| 276 | scope: scopeSetFor(scopeModule), |
| 277 | count: 0, dropped: 0, startedAt: tms(), |
| 278 | }; |
| 279 | stalked.set(tid, state); |
| 280 | stalkedCount++; |
| 281 | const scopeDesc = state.scope ? [...state.scope].join(",") : "ALL"; |
| 282 | console.log(`${C.green}[stalk+]${C.reset} ${PID}:${state.name}#${tid} ${C.dim}scope=${scopeDesc} @+${state.startedAt}ms${C.reset}`); |
| 283 | |
| 284 | // Exclude all non-target modules now — the target lib is guaranteed loaded |
| 285 | // here (this thread's entry lives in it), unlike at script load time. |
| 286 | excludeAllButTargetsOnce(); |
| 287 | |
| 288 | try { |
| 289 | Stalker.follow(tid, { |
| 290 | events: eventsConfig(), |
| 291 | onReceive(events) { |
| 292 | drainEvents(tid, state, events); |
| 293 | }, |
| 294 | }); |
| 295 | } catch (e) { |
| 296 | console.log(`${C.red}[stalk] follow ${tid} failed: ${e}${C.reset}`); |
| 297 | stalked.delete(tid); |
| 298 | stalkedCount--; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | function drainEvents(tid, state, events) { |
| 303 | let parsed; |
no test coverage detected