- 变更规模:新增 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/ 知识文件,按口径入库)
92 lines
3.5 KiB
Python
92 lines
3.5 KiB
Python
"""打印某个 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())
|