Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions installpydep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#! /usr/bin/env bash
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
pip install --no-index --find-links=${SCRIPT_DIR}/offline_packages -r requirements.txt
78 changes: 78 additions & 0 deletions module/profiling/tool/cmd_hijack_launcher/cmd_hijack_launcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
import os
import sys
import shutil
import subprocess
import datetime

def main():
script_path = os.path.realpath(__file__)
script_dir = os.path.dirname(script_path)
config_path = os.path.join(script_dir, "config.yaml")

cmd = os.path.basename(sys.argv[0])
cmd_args = sys.argv[1:]

# 清除自身环境变量
env_path = os.environ.get("PATH", "")
paths = []
for path in env_path.split(os.path.pathsep):
if os.path.realpath(path) != script_dir:
paths.append(path)
env_path_clean = os.path.pathsep.join(paths)

# 查找被劫持的原始命令
hijacked_cmd = shutil.which(cmd, path=env_path_clean)
if not hijacked_cmd:
print(f"Error: can't find hijacked command \"{hijacked_cmd}\" in system PATH", file=sys.stderr)
sys.exit(127)

# 打印基础信息
pid = os.getpid()
print(f"Pid: {pid}")
print(f"Hijacked cmd: {cmd}")
print(f"Current path: {os.getcwd()}")

# 解析配置文件
launched_cmds = []
if os.path.isfile(config_path):
try:
import yaml
with open(config_path, "r", encoding="utf-8") as f:
config = yaml.load(f, Loader=yaml.SafeLoader)
if config and isinstance(config, dict):
launched_cmds = config.get("launch", [])
except Exception as e:
print(f"Error: failed to load config.yaml. (code: {e.errno}. {e.lower()})", file=sys.stderr)
exit()

# 构建命令格式化map
now = datetime.datetime.now()
timestamp = str(int(now.timestamp())).zfill(10)
datetime_fmt_chars = "YymdHMS"
fmt = {"pid": str(pid), "cmd": cmd, "timestamp": timestamp}
for ch in datetime_fmt_chars:
fmt[ch] = now.strftime(f"%{ch}")

# 发射命令
for i, lcmd in enumerate(launched_cmds):
if not isinstance(lcmd, str) or not lcmd.strip():
continue

try:
lcmd = lcmd.format_map(fmt)
lcmd_name = os.path.splitext(os.path.basename(lcmd.split()[0]))[0]
log_file = f"launched-{now.strftime(f"%y%m%d%H%M%S")}-{pid}-{i}-{lcmd_name}-screen.log"
with open(log_file, "w", encoding="utf-8") as f:
# TODO(resserops): 是否支持shell解释,通过yaml配置决策
subprocess.Popen(lcmd, stdout=f, stderr=subprocess.STDOUT, shell=True, close_fds=True)
print(f"Cmd '{lcmd_name}' successfully launched")
except Exception as e:
print(f"Error: failed to launch command '{lcmd}' (code: {e.errno}. {e.lower()})", file=sys.stderr)

# 执行被劫持的原始命令
print()
os.execv(hijacked_cmd, [hijacked_cmd] + cmd_args)

if __name__ == "__main__":
main()
10 changes: 10 additions & 0 deletions module/profiling/tool/cmd_hijack_launcher/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# launch (list(str)): 发射命令配置,每个字符串必须是可执行的shell命令,支持{}格式化
# {pid}: 命令pid
# {cmd}: 命令名称
# {timestamp}: 10位时间戳
# {Y}, {y}, {m}, {d}, {H}, {M}, {S}: 调起命令的日期时间
launch:
- "while kill -0 {pid} 2>/dev/null; do ps -p {pid} -o pid,%cpu,%mem,stat,command; sleep 1; done"

# screen_log_path (str): 发射命令屏幕输出日志保存路径,支持{}格式化
screen_log_path: "."
174 changes: 174 additions & 0 deletions module/profiling/tool/cmd_hijack_launcher/hijack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
import argparse
import os
import sys
import shutil
import filecmp
import subprocess

def remove_entry(entry):
if os.path.isdir(entry) and not os.path.islink(entry):
shutil.rmtree(entry)
else:
os.remove(entry)

def main():
wrapper_name = "cmd_hijack_launcher.py"
setup_script_name = "setup.sh"
critical_files = (wrapper_name, setup_script_name, "config.yaml")

# 参数解析
program = argparse.ArgumentParser(description="deploy symlinks for command hijacking to the specified prefix directory")
program.add_argument("-p", "--prefix", default=os.path.expanduser(os.path.join("~", ".local", "bin", os.path.splitext(wrapper_name)[0])), help=f"installation prefix directory")
program.add_argument("-c", "--clean", action="store_true", help=f"clean up obsolete hijack symlinks in the prefix directory that are not specified in the current command list")
program.add_argument("-f", "--force", action="store_true", help=f"force deployment by overwriting existing files, symlinks, or directories if conflicts occur")
program.add_argument("commands", nargs="*", help="commands to hijack. must be executable in the system PATH")
args = program.parse_args()

# 打印基础信息
script_dir = os.path.dirname(os.path.realpath(__file__))
prefix_dir = os.path.abspath(args.prefix)

print(f"Script dir: {script_dir}")
print(f"Prefix dir: {prefix_dir}\n")

# 创建prefix(安装路径)
if os.path.exists(prefix_dir) or os.path.islink(prefix_dir):
if not os.path.isdir(prefix_dir):
if args.force:
try:
os.remove(prefix_dir)
os.makedirs(prefix_dir, exist_ok=True)
except OSError as e:
print(f"Error: failed to force replace prefix directory '{prefix_dir}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)
sys.exit(1)
else:
print(f"Error: prefix path exists but is not a directory '{prefix_dir}'. use -f to overwrite", file=sys.stderr)
sys.exit(1)
else:
try:
os.makedirs(prefix_dir, exist_ok=True)
except OSError as e:
print(f"Error: cannot create directory '{prefix_dir}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)
sys.exit(1)

# 复制关键脚本到prefix
def copy_to_prefix(f):
src = os.path.join(script_dir, f)
# 检查src存在
if not os.path.exists(src):
print(f"Error: critical file '{f}' not found, hijack failed", file=sys.stderr)
sys.exit(1)

dst = os.path.join(prefix_dir, f)
if os.path.exists(dst) or os.path.islink(dst):
if os.path.isfile(dst) and filecmp.cmp(src, dst):
# 如果已存在文件相同,直接跳过
return
if args.force:
try:
remove_entry(dst)
except OSError as e:
print(f"Error: failed to force replace critical file '{f}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)
return
else:
print(f"Warning: critical file '{f}' already exists. use -f to overwrite", file=sys.stderr)
return

try:
shutil.copy2(src, dst) # 使用copy2保留原始文件权限
except OSError as e:
print(f"Error: failed to copy critical file. (code: {e.errno}, {e.lower()})", file=sys.stderr)
return

for f in critical_files:
copy_to_prefix(f)

# 清理未在commands中的软链接
exists_cmds = []
link_target = os.path.join(".", wrapper_name)

try:
for entry in os.scandir(prefix_dir):
if entry.is_symlink() and entry.name not in args.commands and os.readlink(entry.path) == link_target:
if args.clean:
try:
os.remove(entry.path)
print(f"Command '{entry.name}' unhijacked")
except OSError:
print(f"Error: failed to remove symlink for '{entry.name}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)
continue
else:
exists_cmds.append(entry.name)
except OSError as e:
print(f"Error: failed to scan prefix directory for cleanup. (code: {e.errno}, {e.lower()})", file=sys.stderr)

# 设置软链接
succ_cmds = []
for cmd in args.commands:
if cmd == wrapper_name:
print(f"Warning: skipping self-hijack of '{cmd}'", file=sys.stderr)
continue

if cmd == "python" or cmd == "python3":
print(f"Warning: skipping hijack command '{cmd}' to prevent infinite recursion loop", file=sys.stderr)
continue

if not shutil.which(cmd):
print(f"Warning: command '{cmd}' not found in system PATH. skipping hijack", file=sys.stderr)
continue

try:
# 不允许劫持shell内建命令和关键字
res = subprocess.run(["bash", "-c", f"type -t {cmd}"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)
cmd_type = res.stdout.strip()

if cmd_type == "keyword":
print(f"Warning: skipping hijack command '{cmd}' because it is a shell keyword", file=sys.stderr)
continue
if cmd_type == "builtin":
print(f"Warning: skipping hijack command '{cmd}' because it is a shell builtin command", file=sys.stderr)
continue
except OSError as e:
print(f"Error: failed to check command type for '{cmd}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)

link_name = os.path.join(prefix_dir, cmd)
if os.path.islink(link_name) and os.readlink(link_name) == link_target:
# 已经存在且正确的链接可以忽略
exists_cmds.append(cmd)
continue

if os.path.exists(link_name) or os.path.islink(link_name):
# 不是链接,或者是链接但并未链接到目标
if args.force:
try:
remove_entry(link_name)
except OSError as e:
print(f"Error: failed to force replace hijack link '{cmd}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)
continue
elif os.path.islink(link_name):
print(f"Error: hijack link '{f}' already exists. use -f to overwrite '{cmd}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)
continue

# 建立软链接
try:
os.symlink(link_target, link_name)
succ_cmds.append(cmd)
except OSError as e:
print(f"Error: failed to create symlink for '{cmd}'. (code: {e.errno}, {e.lower()})", file=sys.stderr)

exists_cmds.sort()
for cmd in exists_cmds:
print(f"Command '{cmd}' already hijacked")

succ_cmds.sort()
for cmd in succ_cmds:
print(f"Command '{cmd}' successfully hijacked")

if len(succ_cmds) == 0 and len(exists_cmds) == 0:
print("No commands hijacked")
else:
print(f"To enable and activate the hijacked commands, please run: 'source {os.path.join(prefix_dir, setup_script_name)}'")

if __name__ == "__main__":
main()
8 changes: 8 additions & 0 deletions module/profiling/tool/cmd_hijack_launcher/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
echo "Error: The script must be sourced. please run: 'source $0'"
exit 1
fi

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
export PATH="${SCRIPT_DIR}:${PATH}"
7 changes: 2 additions & 5 deletions module/profiling/tool/monitor/monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ void PlotCpu() {
}
}

py::scoped_interpreter guard{}; // 必须放在try-catch块外侧,否则无法捕获py::error_already_set异常
try {
py::scoped_interpreter guard{}; // 绘图函数仅执行单次
auto plt{py::module_::import("matplotlib.pyplot")};
auto subplots{plt.attr("subplots")(
valid_idxs.size(), 1, py::arg("sharex") = true,
Expand Down Expand Up @@ -440,10 +440,7 @@ void PlotCpu() {
plt.attr("xlabel")("Time (s)");
plt.attr("tight_layout")();
plt.attr("show")();
} catch (const py::error_already_set &e) {
std::cerr << "Error: " << e.what() << std::endl;
exit(1);
}
} catch (const py::error_already_set &e) { std::cerr << "Error: " << e.what() << std::endl; }
}

void Report() {
Expand Down
4 changes: 4 additions & 0 deletions packpydep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#! /usr/bin/env bash
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
mkdir -p ${SCRIPT_DIR}/pydep
pip download -d ${SCRIPT_DIR}/pydep -r ${SCRIPT_DIR}/requirements.txt
Loading