Files
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

92 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""打印某个 PID 的父链,并标注**每一级是否在作业对象里**。
用法:
python proc_chain.py <pid> [depth] # depth 默认 6
为什么需要它 —— 这是本技能的核心判据:
· 链条顶部(`svchost.exe` / `services.exe` / `wininit.exe` 那一层)出现 `in_job=False`
⇒ 该进程**不属于任何会话上下文** ⇒ 它会活过会话收尾(= 真常驻)。
· 若整条链 `in_job=True`、且父级是宿主/沙箱进程(如 `sandbox-cli.exe`)
⇒ 它随那个上下文一起死。
⚠️ 反直觉但很重要:**服务自身 `in_job=True` 并不代表失败**。当服务由计划任务托管时,
`服务 → powershell.exe` 两级都会是 `in_job=True`,但那是**计划任务自己的作业对象**,
只要再往上的 `svchost.exe`(计划任务服务)是 `in_job=False`,就已经脱离宿主了。
⛔ 只读:只用 `PROCESS_QUERY_LIMITED_INFORMATION` 打开进程,不做任何修改。
"""
from __future__ import annotations
import ctypes
import ctypes.wintypes as w
import os
import sys
_k = ctypes.WinDLL("kernel32", use_last_error=True)
_n = ctypes.WinDLL("ntdll")
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
class _PROCESS_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("Reserved1", ctypes.c_void_p),
("PebBaseAddress", ctypes.c_void_p),
("Reserved2", ctypes.c_void_p * 2),
("UniqueProcessId", ctypes.c_void_p),
("InheritedFromUniqueProcessId", ctypes.c_void_p),
]
def inspect(pid: int) -> tuple[int, str, bool] | None:
"""返回 (父 PID, 自身镜像路径, 是否在作业对象内);打不开则 None。"""
h = _k.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
if not h:
return None
try:
injob = w.BOOL(0)
_k.IsProcessInJob(h, None, ctypes.byref(injob))
pbi = _PROCESS_BASIC_INFORMATION()
size = w.DWORD(0)
_n.NtQueryInformationProcess(h, 0, ctypes.byref(pbi), ctypes.sizeof(pbi), ctypes.byref(size))
ppid = int(pbi.InheritedFromUniqueProcessId or 0)
buf = ctypes.create_unicode_buffer(1024)
ln = w.DWORD(len(buf))
name = buf.value if _k.QueryFullProcessImageNameW(h, 0, buf, ctypes.byref(ln)) else "<?>"
return ppid, name, bool(injob.value)
finally:
_k.CloseHandle(h)
def main() -> int:
if len(sys.argv) < 2 or not sys.argv[1].strip().isdigit():
print(__doc__)
return 2
pid = int(sys.argv[1])
depth = int(sys.argv[2]) if len(sys.argv) > 2 and sys.argv[2].isdigit() else 6
print(f"起始 PID = {pid}")
for level in range(depth):
info = inspect(pid)
if info is None:
print(f" {' ' * level}PID {pid} —— 打不开(进程已退出,或权限不足)")
return 0
ppid, name, injob = info
mark = "在作业对象内" if injob else "★ 不在任何作业对象内"
print(f" {' ' * level}PID {pid:<7} in_job={injob} {mark}")
print(f" {' ' * level} └─ 镜像: {name}")
if not ppid:
break
pid = ppid
print()
print("判据:链条顶部出现「不在任何作业对象内」= 已脱离会话上下文(真常驻);")
print(" 反之为「在作业对象内」时,看它的父级 —— 若父级是宿主/沙箱进程(sandbox-cli.exe 等),")
print(" 则该进程会随那个上下文一起被杀。")
return 0
if __name__ == "__main__":
sys.exit(main())