OpenClaw GateWaytang@tang-vm:~$ ls -la /home/tang/.nvm/versions/node/v22.22.0/bin/openclawlrwxrwxrwx 1 tang tang 41 /home/tang/.nvm/versions/node/v22.22.0/bin/openclaw -> ../lib/node_modules/openclaw/openclaw.mjs
tang@tang-vm:~$ grep -A3 '"bin"' /home/tang/.nvm/versions/node/v22.22.0/lib/node_modules/openclaw/package.json"bin": {"openclaw": "openclaw.mjs"},"directories": {
// home/tang/opt/openclawsource/openclaw/src/entry.tsfunction tryHandleRootVersionFastPath(argv: string[]): boolean {if (!isRootVersionInvocation(argv)) {return false;}import("./version.js").then(({ VERSION }) => {console.log(VERSION);})
if (!tryHandleRootVersionFastPath(process.argv) && !tryHandleRootHelpFastPath(process.argv)) {import("./cli/run-main.js").then(({ runCli }) => runCli(process.argv))
// /home/tang/opt/openclawsource/openclaw/src/infra/runtime-guard.tsexportfunctionassertSupportedRuntime(runtime: RuntimeEnv = defaultRuntime,details: RuntimeDetails = detectRuntime(),): void {//省略runtime.error(["openclaw requires Node >=22.12.0.",`Detected: ${runtimeLabel} (exec: ${execLabel}).`,`PATH searched: ${details.pathEnv}`,"Install Node: https://nodejs.org/en/download","Upgrade Node and re-run openclaw.",].join("\n"),);runtime.exit(1);}
// vim ~/.openclaw/extensions/openclaw-qqbot/src/channel.ts// 追踪runcli调用QQ堆栈如下gateway: {startAccount: async (ctx) => {const { account, abortSignal, log, cfg } = ctx;console.trace("QQ starting gateway************************************************************");log?.info(`[qqbot:${account.accountId}] Starting gateway — appId=${account.appId}, enabled=${account.enabled}, name=${account.name ?? "unnamed"}`);console.log(`[qqbot:channel] startAccount: accountId=${account.accountId}, appId=${account.appId}, secretSource=${account.secretSource}`);//它这里对应的QQ日志08:26:25 [qqbot] [qqbot:default] Starting gateway — appId=xxx, enabled=true, name=unnamed08:26:25 [qqbot:channel] startAccount: accountId=default, appId=xxx, secretSource=config
// vim /home/tang/.openclaw/extensions/openclaw-qqbot/src/gateway.tsws.on("open", () => {log?.info(`[qqbot:${account.accountId}] WebSocket connected`);isConnecting = false; // 连接完成,释放锁reconnectAttempts = 0; // 连接成功,重置重试计数lastConnectTime = Date.now(); // 记录连接时间// 启动消息处理器(异步处理,防止阻塞心跳)startMessageProcessor(handleMessage);// P1-1: 启动后台 Token 刷新startBackgroundTokenRefresh(account.appId, account.clientSecret, {log: log as { info: (msg: string) => void; error: (msg: string) => void; debug?: (msg: string) => void },});});
// vim /home/tang/.openclaw/extensions/openclaw-qqbot/src/gateway.tsconst dispatchPromise = pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({ctx: ctxPayload,
$ cat ~/.openclaw/extensions/openclaw-qqbot/index.tsimport type { OpenClawPluginApi } from "openclaw/plugin-sdk";import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";import { qqbotPlugin } from "./src/channel.js";import { setQQBotRuntime } from "./src/runtime.js";const plugin = {id: "openclaw-qqbot",name: "QQ Bot",description: "QQ Bot channel plugin",configSchema: emptyPluginConfigSchema(),register(api: OpenClawPluginApi) {setQQBotRuntime(api.runtime);api.registerChannel({ plugin: qqbotPlugin });},};
// ~/.openclaw/extensions/openclaw-qqbot/src/channel.tsawait startGateway({account,abortSignal,cfg,log,onReady: () => {log?.info(`[qqbot:${account.accountId}] Gateway ready`);ctx.setStatus({...ctx.getStatus(),running: true,connected: true,lastConnectedAt: Date.now(),});},onError: (error) => {log?.error(`[qqbot:${account.accountId}] Gateway error: ${error.message}`);ctx.setStatus({...ctx.getStatus(),lastError: error.message,});},});
// ~/.openclaw/extensions/openclaw-qqbot/src/gateway.tsexport async function startGateway(ctx: GatewayContext): Promise<void> {const { account, abortSignal, cfg, onReady, onError, log } = ctx;if (!account.appId || !account.clientSecret) {throw new Error("QQBot not configured (missing appId or clientSecret)");}// 启动环境诊断(首次连接时执行)const diag = await runDiagnostics();if (diag.warnings.length > 0) {for (const w of diag.warnings) {log?.info(`[qqbot:${account.accountId}] ${w}`);}}// 后台版本检查(供 /bot-version、/bot-upgrade 指令被动查询)triggerUpdateCheck(log);// 初始化 API 配置(markdown 支持)initApiConfig({markdownSupport: account.markdownSupport,});log?.info(`[qqbot:${account.accountId}] API config: markdownSupport=${account.markdownSupport === true}`);
// src/process/command-queue.tsfunction drainLane(lane) {//用于堆栈跟踪console.trace("drainlane*********************************************************************标签");const state = getLaneState(lane);if (state.draining) {if (state.activeTaskIds.size === 0 && state.queue.length > 0) diag.warn(`drainLane blocked: lane=${lane} draining=true active=0 queue=${state.queue.length}`);return;}const entry = state.queue.shift();const result = await entry.task(); //调用任务}
// src/process/command-queue.ts(async () => {const startTime = Date.now();try {const result = await entry.task();if (completeTask(state, taskId, taskGeneration)) {diag.debug(`lane task done: lane=${lane} durationMs=${Date.now() - startTime} active=${state.activeTaskIds.size} queued=${state.queue.length}`);pump();}entry.resolve(result);} catch (err) {const completedCurrentGeneration = completeTask(state, taskId, taskGeneration);if (!(lane.startsWith("auth-probe:") || lane.startsWith("session:probe-"))) diag.error(`lane task error: lane=${lane} durationMs=${Date.now() - startTime} error="${String(err)}"`);if (completedCurrentGeneration) pump();entry.reject(err);}})();
/*runEmbeddedAttempt(单次尝试)├── 解析工作目录├── 初始化沙箱├── 加载 skills├── 加载 bootstrap 文件├── 创建工具(createOpenClawCodingTools)├── buildEmbeddedSystemPrompt(构建 system prompt)└── 交给 pi-agent-core runLoop 驱动模型*/// src/agents/pi-embedded-runner/run.tsasync function runEmbeddedPiAgent(params) {const attempt = await runEmbeddedAttempt({sessionId: params.sessionId,sessionKey: params.sessionKey...}
toToolDefinitions。
// src/agents/pi-embedded-runner/run.tsasync function runEmbeddedAttempt(params) {const appendPrompt = buildEmbeddedSystemPrompt({});//中间省略const { builtInTools, customTools } = splitSdkTools({tools,sandboxEnabled: !!sandbox?.enabled});}----------------------------------------function splitSdkTools(options) {const { tools } = options;return {builtInTools: [],customTools: toToolDefinitions(tools)};}
toToolDefinitions就会调用工具跟大模型进行一个对话了
// src/agents/pi-tool-definition-adapter.tsfunction toToolDefinitions(tools) {// 便于堆栈追踪console.trace("toToolDefinitionstoToolDefinitions********************************************标签");//中间省略return normalizeToolExecutionResult({toolName: normalizedName,/ // 这个地方调用工具result: await tool.execute(toolCallId, executeParams, signal, onUpdate)});
比如我提问:
帮我回忆下我们之前聊过什么或者 调用memory_search工具搜索下:伊朗
那么此时就会调用工具memory_search,代码
//src/memory/manager.tsasync search(query, opts) {// 用于堆栈追踪console.trace("**************************************************************search到这里了 query=" + query);
比如说我们要跟踪记忆数据库的话,可以
// src/memory/manager-sync-ops.tsopenDatabaseAtPath(dbPath) {console.trace("**************************************************************到这里了");debugger;ensureDir(path.dirname(dbPath));const { DatabaseSync } = requireNodeSqlite();return new DatabaseSync(dbPath, { allowExtension: this.settings.store.vector.enabled });}
这里面有一个构造函数的实例化数据库
constructor(params) {this.db = this.openDatabase();}
这个构造函数是在下面被初始化的
// src/agents/tools/memory-tool.tsfunction createMemorySearchTool(options) {const ctx = resolveMemoryToolContext(options);// 中间省略});const rawResults = await manager.search(query, {//要跟踪到这个代码 它的堆栈如下async search(query, opts) {console.trace("**************************************************************search到这里了 query=" + query);
我们的drainLane是何时被调用的呢?追踪如下
function drainLane(lane) {console.trace("drainlane*********************************************************************标签");
最终的第一个堆栈是
// src/cli/gateway-cli/run.tsfunction addGatewayRunCommand(cmd) {return cmd.option("--port <port>", "Port for the gateway WebSocket").option("--bind <mode>", "Bind mode (\"loopback\"|\"lan\"|\"tailnet\"|\"auto\"|\"custom\"). Defaults to config gateway.bind (or loopback).").option("--token <token>", "Shared token required in connect.params.auth.token (default: OPENCLAW_GATEWAY_TOKEN env if set)").option("--auth <mode>", `Gateway auth mode (${formatModeChoices(GATEWAY_AUTH_MODES)})`).option("--password <password>", "Password for auth mode=password").option("--tailscale <mode>", `Tailscale exposure mode (${formatModeChoices(GATEWAY_TAILSCALE_MODES)})`).option("--tailscale-reset-on-exit", "Reset Tailscale serve/funnel configuration on shutdown", false).option("--allow-unconfigured", "Allow gateway start without gateway.mode=local in config", false).option("--dev", "Create a dev config + workspace if missing (no BOOTSTRAP.md)", false).option("--reset", "Reset dev config + credentials + sessions + workspace (requires --dev)", false).option("--force", "Kill any existing listener on the target port before starting", false).option("--verbose", "Verbose logging to stdout/stderr", false).option("--claude-cli-logs", "Only show claude-cli logs in the console (includes stdout/stderr)", false).option("--ws-log <style>", "WebSocket log style (\"auto\"|\"full\"|\"compact\")", "auto").option("--compact", "Alias for \"--ws-log compact\"", false).option("--raw-stream", "Log raw model stream events to jsonl", false).option("--raw-stream-path <path>", "Raw stream jsonl path").action(async (opts, command) => {await runGatewayCommand$1(resolveGatewayRunOptions(opts, command));});}
这实际上是一个命令行的传入。
比如:OpenClaw GateWay --port 8888等。
搞了这么久,还没看到跟大模型交互。实际的交互代码如下:
cat ~/opt/openclawsource/openclaw/node_modules/.pnpm/@mariozechner+pi-agent-core@0.55.3_ws@8.19.0_zod@4.3.6/node_modules/@mariozechner/pi-agent-core/dist/agent-loop.jsasync function streamAssistantResponse(context, config, signal, stream, streamFn) {streamFunction() //这里跟大模型交互 可以追踪}
那么大致
drainLane--》await entry.task()--》runEmbeddedPiAgent--》runEmbeddedAttempt--》buildEmbeddedSystemPrompt和splitSdkTools--》toToolDefinitions--》 tool.execute--》manager.search(query--》
import asyncioimport aiohttpimport jsonimport time# 从 OpenClaw 配置里拿这两个值APP_ID = "xxx"CLIENT_SECRET = "xxxxx"# ===== 1. 获取 Access Token =====async def get_access_token(session):resp = await session.post("https://bots.qq.com/app/getAppAccessToken",json={"appId": APP_ID, "clientSecret": CLIENT_SECRET})data = await resp.json()return data["access_token"]# ===== 2. 获取 WebSocket 网关地址 =====async def get_gateway_url(session, token):resp = await session.get("https://api.sgroup.qq.com/gateway",headers={"Authorization": f"QQBot {token}"})data = await resp.json()return data["url"]# ===== 3. 发送消息给用户 =====async def send_message(session, token, user_id, content, msg_id):await session.post(f"https://api.sgroup.qq.com/v2/users/{user_id}/messages",headers={"Authorization": f"QQBot {token}"},json={"content": content,"msg_type": 0,"msg_id": msg_id,"msg_seq": int(time.time()),})# ===== 4. 处理收到的消息 =====async def handle_message(session, token, event):user_id = event["author"]["id"]content = event["content"].strip()msg_id = event["id"]print(f"收到消息 from {user_id}: {content}")# 在这里写你的业务逻辑# reply = f"你说的是:{content}"# Please install OpenAI SDK first: `pip3 install openai`import osfrom openai import OpenAIclient = OpenAI(api_key="sk-xxxxxx",base_url="https://api.deepseek.com")response = client.chat.completions.create(model="deepseek-chat",messages=[{"role": "system", "content": "You are a helpful assistant"},{"role": "user", "content": content},],stream=False)# print(response.choices[0].message.content)await send_message(session, token, user_id, response.choices[0].message.content, msg_id)# ===== 5. WebSocket 主循环 =====async def main():async with aiohttp.ClientSession() as session:token = await get_access_token(session)print(f"Token 获取成功")gateway_url = await get_gateway_url(session, token)print(f"Gateway: {gateway_url}")async with session.ws_connect(gateway_url) as ws:heartbeat_interval = Noneasync for msg in ws:data = json.loads(msg.data)op = data.get("op")t = data.get("t")d = data.get("d", {})# OP=10: 服务器发来 Hello,开始心跳if op == 10:heartbeat_interval = d["heartbeat_interval"] / 1000asyncio.create_task(heartbeat(ws, heartbeat_interval))# 发送 Identifyawait ws.send_json({"op": 2,"d": {"token": f"QQBot {token}","intents": 1107300352, # 群聊+私信+频道,和 OpenClaw 一样"shard": [0, 1],}})# OP=0: 事件派发elif op == 0:if t == "C2C_MESSAGE_CREATE": # 私聊消息await handle_message(session, token, d)elif t == "GROUP_AT_MESSAGE_CREATE": # 群@消息print(f"群消息: {d}")elif t == "READY":print(f"Bot 已就绪: {d['user']['username']}")# OP=11: 心跳 ACKelif op == 11:print("心跳 ACK")# ===== 心跳 =====async def heartbeat(ws, interval):while True:await asyncio.sleep(interval)await ws.send_json({"op": 1, "d": None})print("发送心跳")asyncio.run(main())
import asyncioimport aiohttpimport jsonimport time# 从 OpenClaw 配置里拿这两个值APP_ID = "xxxx"CLIENT_SECRET = "xxxxxxx"# ===== 1. 获取 Access Token =====async def get_access_token(session):resp = await session.post("https://bots.qq.com/app/getAppAccessToken",json={"appId": APP_ID, "clientSecret": CLIENT_SECRET})data = await resp.json()return data["access_token"]# ===== 2. 获取 WebSocket 网关地址 =====async def get_gateway_url(session, token):resp = await session.get("https://api.sgroup.qq.com/gateway",headers={"Authorization": f"QQBot {token}"})data = await resp.json()return data["url"]# ===== 3. 发送消息给用户 =====async def send_message(session, token, user_id, content, msg_id):await session.post(f"https://api.sgroup.qq.com/v2/users/{user_id}/messages",headers={"Authorization": f"QQBot {token}"},json={"content": content,"msg_type": 0,"msg_id": msg_id,"msg_seq": int(time.time()),})import inspect, jsonfrom openai import OpenAI# ---- helper ----def make_tool(func):sig = inspect.signature(func)props = {name: {"type": "string", "description": name}for name in sig.parameters}return {"type": "function","function": {"name": func.__name__,"description": func.__doc__ or func.__name__,"parameters": {"type": "object","properties": props,"required": list(props.keys())}}}import subprocessimport os# ---- 定义工具函数 ----def get_weather(city: str) -> str:"""获取指定城市的天气"""return f"{city} 今天晴天 25°C"def calculator(expression: str) -> str:"""计算数学表达式"""return str(eval(expression))def open_cmd(command: str = "") -> str:"""打开cmd窗口,可选传入要执行的命令"""if command:os.system(f'start cmd /k "{command}"')return f"已打开cmd并执行: {command}"else:os.system("start cmd")return "已打开cmd窗口"def run_cmd(command: str) -> str:"""在后台执行cmd命令并返回结果"""result = subprocess.run(command, shell=True,capture_output=True, text=True, timeout=30)return result.stdout or result.stderr or "执行完成"# ---- 注册工具 ----tool_funcs = [get_weather, calculator,open_cmd,run_cmd]tools = [make_tool(f) for f in tool_funcs]tool_map = {f.__name__: f for f in tool_funcs}# ===== 4. 处理收到的消息 =====async def handle_message(session, token, event):user_id = event["author"]["id"]content = event["content"].strip()msg_id = event["id"]print(f"收到消息 from {user_id}: {content}")# 在这里写你的业务逻辑# Please install OpenAI SDK first: `pip3 install openai`import osfrom openai import OpenAIclient = OpenAI(api_key="sk-xxxxxxxx",base_url="https://api.deepseek.com")messages = [{"role": "system", "content": "You are a helpful assistant"},{"role": "user", "content": content},]response = client.chat.completions.create(model="deepseek-chat",messages=messages,tools=tools,tool_choice="auto",stream=False)msg = response.choices[0].messageif msg.tool_calls:messages.append(msg)for tool_call in msg.tool_calls:func_name = tool_call.function.namefunc_args = json.loads(tool_call.function.arguments)result = tool_map[func_name](**func_args)messages.append({"role": "tool","tool_call_id": tool_call.id,"content": result})final = client.chat.completions.create(model="deepseek-chat",messages=messages,stream=False)# print(final.choices[0].message.content)await send_message(session, token, user_id, final.choices[0].message.content, msg_id)print(f"发送出去的消息:{final.choices[0].message.content}")else:#(msg.content)await send_message(session, token, user_id, msg.content, msg_id)print(f"发送出去的消息:{msg.content}")# ===== 5. WebSocket 主循环 =====async def main():async with aiohttp.ClientSession() as session:token = await get_access_token(session)print(f"Token 获取成功")gateway_url = await get_gateway_url(session, token)print(f"Gateway: {gateway_url}")async with session.ws_connect(gateway_url) as ws:heartbeat_interval = Noneasync for msg in ws:data = json.loads(msg.data)op = data.get("op")t = data.get("t")d = data.get("d", {})# OP=10: 服务器发来 Hello,开始心跳if op == 10:heartbeat_interval = d["heartbeat_interval"] / 1000asyncio.create_task(heartbeat(ws, heartbeat_interval))# 发送 Identifyawait ws.send_json({"op": 2,"d": {"token": f"QQBot {token}","intents": 1107300352, # 群聊+私信+频道,和 OpenClaw 一样"shard": [0, 1],}})# OP=0: 事件派发elif op == 0:if t == "C2C_MESSAGE_CREATE": # 私聊消息await handle_message(session, token, d)elif t == "GROUP_AT_MESSAGE_CREATE": # 群@消息print(f"群消息: {d}")elif t == "READY":print(f"Bot 已就绪: {d['user']['username']}")# OP=11: 心跳 ACKelif op == 11:print("心跳 ACK")# ===== 心跳 =====async def heartbeat(ws, interval):while True:await asyncio.sleep(interval)await ws.send_json({"op": 1, "d": None})print("发送心跳")asyncio.run(main())
git clone https://github.com/python/cpython.gitcd cpythoncd cpythonsudo apt updatesudo apt install -y build-essential libssl-dev zlib1g-dev libbz2-dev \libreadline-dev libsqlite3-dev libffi-dev libncurses5-dev libgdbm-dev \liblzma-dev./configure --prefix=$HOME/opt/python-debug --with-pydebugmake -j$(nproc)make install
x = 1 + 2print(x)gdb $HOME/opt/python-debug/bin/python3set args hello.pyb _PyAssemble_MakeCodeObjectb assemble_emitrcfinish //运行完成assemble_emit函数,因下面要查看其字节码(gdb)x/32xb ((PyBytesObject*)a.a_bytecode)->ob_sval0x7ffff6cde880: 0x80 0x00 0x00 0x00 0x5d 0x03 0x73 0x000x7ffff6cde888: 0x5c 0x01 0x1f 0x00 0x5c 0x00 0x32 0x010x7ffff6cde890: 0x00 0x00 0x00 0x00 0x00 0x00 0x1d 0x000x7ffff6cde898: 0x51 0x01 0x21 0x00 0x00 0xfd 0xfd 0xfd
import diswith open("hello.py") as f:src = f.read()code = compile(src, "hello.py", "exec")print(code.co_names)dis.dis(code)import opcode# 查单个 opcode 的名称print(opcode.opname[128]) # → RESUMEprint(opcode.opname[93]) # → FOR_ITERprint(opcode.opname[92]) # → UNPACK_SEQUENCE 或其他# 批量解析你的字节raw = [0x80,0x00, 0x00,0x00, 0x5d,0x03, 0x73,0x00,0x5c,0x01, 0x1f,0x00, 0x5c,0x00, 0x32,0x01]for i in range(0, len(raw), 2):op = raw[i]arg = raw[i+1]print(f" offset={i:3d} opcode={op:3d} ({opcode.opname[op]:30s}) arg={arg}")
E:\python14\python.exe D:\PyCharm\PythonProject7\asm.py('x', 'print')0 RESUME 01 LOAD_SMALL_INT 3STORE_NAME 0 (x)2 LOAD_NAME 1 (print)PUSH_NULLLOAD_NAME 0 (x)CALL 1POP_TOPLOAD_CONST 1 (None)RETURN_VALUERESUMELOAD_NAMELOAD_GLOBALoffset= 0 opcode=128 (RESUME ) arg=0offset= 2 opcode= 0 (CACHE ) arg=0offset= 4 opcode= 93 (LOAD_NAME ) arg=3offset= 6 opcode=115 (STORE_GLOBAL ) arg=0offset= 8 opcode= 92 (LOAD_GLOBAL ) arg=1offset= 10 opcode= 31 (POP_TOP ) arg=0offset= 12 opcode= 92 (LOAD_GLOBAL ) arg=0offset= 14 opcode= 50 (BUILD_STRING ) arg=1进程已结束,退出代码为 0
offset 字节 opcode名称 arg 说明─────────────────────────────────────────────────────────────0 0x80 0x00 RESUME 0 函数入口检查点2 0x00 0x00 CACHE 0 内联缓存占位4 0x5d 0x03 LOAD_SMALL_INT 3 把整数 3 压栈 ← 1+2已在编译期算好6 0x73 0x00 STORE_NAME 0 弹栈存入 x8 0x5c 0x01 LOAD_NAME 1 压栈 print10 0x1f 0x00 PUSH_NULL 0 压入 NULL(调用约定)12 0x5c 0x00 LOAD_NAME 0 压栈 x14 0x32 0x01 CALL 1 调用,参数个数=1… 0x00 ... CACHE×4 CALL 后跟4个缓存槽0x1d 0x00 POP_TOP 0 丢弃 print() 返回值0x51 0x01 LOAD_CONST 1 压栈 None0x21 0x00 RETURN_VALUE 0 返回
x = 1 + 2```**GDB 里看不到加法指令**,因为编译器在 `_PyCfg_OptimizeCodeUnit` 阶段做了**常量折叠**:```优化前: 优化后:LOAD_SMALL_INT 1 LOAD_SMALL_INT 3 ← 直接是3LOAD_SMALL_INT 2 STORE_NAME 0BINARY_OP +STORE_NAME 0
//home/tang/opt/cpython/cpython/Python/generated_cases.c.h比如把整数3压入栈TARGET(LOAD_SMALL_INT) {#if _Py_TAIL_CALL_INTERPint opcode = LOAD_SMALL_INT;(void)(opcode);#endifframe->instr_ptr = next_instr;next_instr += 1;INSTRUCTION_STATS(LOAD_SMALL_INT);_PyStackRef value;assert(oparg < _PY_NSMALLPOSINTS);PyObject *obj = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + oparg];value = PyStackRef_FromPyObjectBorrow(obj);stack_pointer[0] = value;stack_pointer += 1;ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);DISPATCH();}
using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;using System;[DisassemblyDiagnoser]publicpartialclassProgram{staticvoidMain(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);privateint _a = 42, _b = 84;[Benchmark]publicintMin() => Math.Min(_a, _b);}
// 注意这里 releas 如果是debug则测不出来,如果要asm,则 $env:DOTNET_JitDisasm="Main"$env:DOTNET_JitDump="Main"$env:DOTNET_JitDisasm="Main"dotnet run -c release -f net9.0--filter'Program:*'
CORE_LIBRARIES=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\10.0.0-preview.7.25380.108DOTNET_TieredCompilation=1DOTNET_TieredPGO=1DOTNET_ReadyToRun=1DOTNET_JitDump=MainDOTNET_TC_QuickJitForLoops=1DOTNET_EnableWriteXorExecute=0
vim ~/.bashrcexport CORE_LIBRARIES=/usr/lib/dotnet/shared/Microsoft.NETCore.App/10.0.4/export LD_LIBRARY_PATH=/home/tang/opt/dotnet/runtime/artifacts/bin/coreclr/linux.x64.Debug:$LD_LIBRARY_PATH
dotnet tool install --global dotnet-sosdotnet-sos install
vim ~/.lldbinit#START - ADDED BY SOS INSTALLERplugin load /home/tang/.dotnet/sos/libsosplugin.sosetsymbolserver -ms#END - ADDED BY SOS INSTALLER
staticvoidMain(){var sw = new System.Diagnostics.Stopwatch();while (true){sw.Restart();for (int trial = 0; trial < 10_000; trial++){int count = 0;for (int i = 0; i < char.MaxValue; i++)if (IsAsciiDigit((char)i))count++;}sw.Stop();Console.WriteLine(sw.Elapsed);}staticboolIsAsciiDigit(char c) => (uint)(c - '0') <= 9;}
$env:CORE_LIBRARIES="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\10.0.0-preview.7.25380.108"$env:DOTNET_JitDump="Main".\corerun.exe .\测试.dll > jitlog.txt
OSR enabled forthis methodthis 可修改(OSR 要求 this 是只读或者静态方法,确保栈上数据一致 → 如果可修改就不安全),以及如果编译时没有开启 FEATURE_ON_STACK_REPLACEMENT,直接说明 OSR 功能不可用。//inlineboolCompiler::compCanHavePatchpoints(constchar** reason){constchar* whyNot = nullptr;#ifdef FEATURE_ON_STACK_REPLACEMENTif (compLocallocSeen){whyNot = "OSR can't handle localloc";}elseif (compHasBackwardJumpInHandler){whyNot = "OSR can't handle loop in handler";}elseif (opts.IsReversePInvoke()){whyNot = "OSR can't handle reverse pinvoke";}elseif (!info.compIsStatic && !lvaIsOriginalThisReadOnly()){whyNot = "OSR can't handle modifiable this";}#elsewhyNot = "OSR feature not defined in build";#endifif (reason != nullptr){*reason = whyNot;}return whyNot == nullptr;}
Can't set source patchpoint at BB10, using target BB02 insteadif (succBlock->bbStackDepthOnEntry() >0){JITDUMP("\nCan't set source patchpoint at "FMT_BB", can't use target "FMT_BB" as it has non-empty stack on entry.\n",block->bbNum, succBlock->bbNum);}else{JITDUMP("\nCan't set source patchpoint at "FMT_BB", using target "FMT_BB" instead\n",block->bbNum, succBlock->bbNum);assert(!succBlock->hasHndIndex());succBlock->SetFlags(BBF_PATCHPOINT);}
*************** Starting PHASE Expand patchpointsPatchpoint: regular patchpoint in BB02
// patchpoint.cppif (block->HasFlag(BBF_PATCHPOINT)){// We can't OSR from funclets.//assert(!block->hasHndIndex());// Clear the patchpoint flag.//block->RemoveFlags(BBF_PATCHPOINT);JITDUMP("Patchpoint: regular patchpoint in "FMT_BB"\n", block->bbNum);TransformBlock(block);count++;}
void TransformBlock(BasicBlock* block){// If we haven't allocated the counter temp yet, set it upif (ppCounterLclNum == BAD_VAR_NUM){ppCounterLclNum = compiler->lvaGrabTemp(true DEBUGARG("patchpoint counter"));compiler->lvaTable[ppCounterLclNum].lvType = TYP_INT;// and initialize in the entry blockTransformEntry(compiler->fgFirstBB);}// Capture the IL offsetIL_OFFSET ilOffset = block->bbCodeOffs;assert(ilOffset != BAD_IL_OFFSET);// Current block now becomes the test block// 把这个block切成两个bloc块,bloc块和remainderBlock块BasicBlock* remainderBlock = compiler->fgSplitBlockAtBeginning(block);// 创建一个helperBlock块BasicBlock* helperBlock = CreateAndInsertBasicBlock(BBJ_ALWAYS, block);// Update flow and flagsblock->SetFlags(BBF_INTERNAL);helperBlock->SetFlags(BBF_BACKWARD_JUMP);//这个断言确保block 块的目标是remainderblock 块,这里目标targetassert(block->TargetIs(remainderBlock));// helperblock 块的前置块是block 块// edge表示某条支线或者边, 比如下面falseedge表示 block到helperblock的支线FlowEdge* const falseEdge = compiler->fgAddRefPred(helperBlock, block);// 获取block 块的目标边 也即是 remainderblock,上面断言同样FlowEdge* const trueEdge = block->GetTargetEdge();// 设置这个边的概率,也就是分支预测执行概率 这里大数值,也即是执行比较多。下面的falseEdge小数值 也就是执行比较少trueEdge->setLikelihood(HIGH_PROBABILITY / 100.0);falseEdge->setLikelihood((100 - HIGH_PROBABILITY) / 100.0);block->SetCond(trueEdge, falseEdge);// 设置remainderBlock 块的前置块是helperblockFlowEdge* const newEdge = compiler->fgAddRefPred(remainderBlock, helperBlock);//再设置helperblock的目标是remainderblock,这样 remainderblock就有两条支线到达了helperBlock->SetTargetEdge(newEdge);// Update weightsremainderBlock->inheritWeight(block);helperBlock->inheritWeightPercentage(block, 100 - HIGH_PROBABILITY);// Fill in test block//// --ppCounter;GenTree* ppCounterBefore = compiler->gtNewLclvNode(ppCounterLclNum, TYP_INT);GenTree* one = compiler->gtNewIconNode(1, TYP_INT);// 表示ppCounterBefore-one ppCounterSub 表示一个节点GenTree* ppCounterSub = compiler->gtNewOperNode(GT_SUB, TYP_INT, ppCounterBefore, one);// 表示把 上面ppCounterSub的结果写回ppCounterLclNumGenTree* ppCounterUpdate = compiler->gtNewStoreLclVarNode(ppCounterLclNum, ppCounterSub);// 把这棵树插入到block块的末尾。,下面所有都是以此类推compiler->fgNewStmtAtEnd(block, ppCounterUpdate);// if (ppCounter > 0), bypass helper callGenTree* ppCounterUpdated = compiler->gtNewLclvNode(ppCounterLclNum, TYP_INT);GenTree* zero = compiler->gtNewIconNode(0, TYP_INT);GenTree* compare = compiler->gtNewOperNode(GT_GT, TYP_INT, ppCounterUpdated, zero);GenTree* jmp = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, compare);compiler->fgNewStmtAtEnd(block, jmp);// Fill in helper block//// call PPHelper(&ppCounter, ilOffset)GenTree* ilOffsetNode = compiler->gtNewIconNode(ilOffset, TYP_INT);GenTree* ppCounterAddr = compiler->gtNewLclVarAddrNode(ppCounterLclNum);GenTreeCall* helperCall =compiler->gtNewHelperCallNode(CORINFO_HELP_PATCHPOINT, TYP_VOID, ppCounterAddr, ilOffsetNode);compiler->fgNewStmtAtEnd(helperBlock, helperCall);}
--ppCounter;if (ppCounter > 0), bypass helper callcall PPHelper(&ppCounter, ilOffset)代码原理 :1.先分割block为两个block(block和remainderblock): fgSplitBlockAtBeginning注意这里的分割之后,block的目标(target)依旧是remainderblock,这点可以通过断言看出来assert(block->TargetIs(remainderBlock))2.新建一个block(helperblock):CreateAndInsertBasicBlock3.把helperblock插入到block和remainderblock的中间,进行了分支预测。trueedge和falsedge4.像这种:gtNewLclvNode表示新建一个本地(Lcl)变量(v)节点(Node)gtNewIconNode新建一个常量(Icon)节点(Node)
***** BB12 [0011]STMT00024 ( ??? ... ??? )[000075] --CXG------ * CALL help void CORINFO_HELP_PATCHPOINT[000074] ----------- arg0 +--* LCL_ADDR long V07 tmp3 [+0][000073] ----------- arg1 \--* CNS_INT int6------------ BB11 [0010] [006..010) -> BB09(1) (always), preds={BB02,BB12} succs={BB09}
G_M000_IG04: ;; offset=0x0059lea rcx, [rbp-0x68]mov edx, 6call CORINFO_HELP_PATCHPOINT
RtlCaptureContext获取当前所在帧栈状态的contextVirtualUnwindToFirstManagedCallFrame找到当前调用堆栈的第一个托管帧栈(这里是main),如果中间有非托管直接跳过CallDescrWorker获取调用当前函数的函数帧栈,也即是main的上一层(CallDescrWorkerInternalReturnAddress)
// 取代SetIP(pFrameContext, osrMethodCode)// 执行ClrRestoreNonvolatileContext(pFrameContext);
NESTED_ENTRY JIT_Patchpoint, _TEXTPROLOG_WITH_TRANSITION_BLOCKlea rcx, [rsp + __PWTB_TransitionBlock] ; TransitionBlock *call JIT_PatchpointWorkerWorkerWithPolicyEPILOG_WITH_TRANSITION_BLOCK_RETURNTAILJMP_RAXNESTED_END JIT_Patchpoint, _TEXT
extern "C" voidJIT_PatchpointWorkerWorkerWithPolicy(TransitionBlock * pTransitionBlock){osrMethodCode = PatchpointOptimizationPolicy(pTransitionBlock, counter, ilOffset, ppInfo, codeInfo, &isNewMethod);CONTEXT *pFrameContext = NULL;success = g_pfnInitializeContext2 ?g_pfnInitializeContext2(pBuffer, contextFlags, &pFrameContext, &contextSize, xStateCompactionMask) :InitializeContext(pBuffer, contextFlags, &pFrameContext, &contextSize);//获取调用当前函数的快照RtlCaptureContext(pFrameContext);// 回溯到当前堆栈第一次托管函数调用的帧栈,也即是mainpThread->VirtualUnwindToFirstManagedCallFrame(pFrameContext);// 向上回溯一次,来到了clr调用Main函数的地方,也即是CallDescrWorkerInternalReturnAddress// 所以此时的rip是CallDescrWorkerInternalReturnAddress函数头所在的地址RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(pFrameContext), callerCodeInfo.GetFunctionEntry(),pFrameContext, &handlerData, &establisherFrame, NULL);// 设置优化后的代码,也即是osr的函数头地址SetIP(pFrameContext, osrMethodCode);// 设置好了函数头,下面就调用函数头ClrRestoreNonvolatileContext(pFrameContext);}
// 这个是设置程序循环或者运行了多少次才会触发jit_patchpoint调用 默认是0x3e8也即是1000次const int counterBump = g_pConfig->OSR_CounterBump();// 如果jit_patchpoint调用没有10次(hitLimit),则没有osr优化后的机器码// hitLimit也是环境变量, 默认10// hitCount表示每调用一次jit_patchpoint就会加1 ,当等于10 则会有优化的OSR代码// 否则直接返回,也即是goto DONE这个代码const int hitLimit = g_pConfig->OSR_HitLimit();const int hitCount = InterlockedIncrement(&ppInfo->m_patchpointCount);const int hitLogLevel = (hitCount == 1) ? LL_INFO10 : LL_INFO1000;if (hitCount < hitLimit){goto DONE;}后面的调用 JitPatchpointWorker-》pMD->PrepareCode开启了新的JIT编译返回的结果即是osrMehtodCode。
voidCompiler::generatePatchpointInfo(){if (!doesMethodHavePatchpoints() && !doesMethodHavePartialCompilationPatchpoints()){// Nothing to reportreturn;}// Patchpoints are only found in Tier0 code, which is unoptimized, and so// should always have frame pointer.assert(codeGen->isFramePointerUsed());// Allocate patchpoint info storage from runtime, and fill in initial bits of data.constunsigned patchpointInfoSize = PatchpointInfo::ComputeSize(info.compLocalsCount);PatchpointInfo* const patchpointInfo = (PatchpointInfo*)info.compCompHnd->allocateArray(patchpointInfoSize);// Patchpoint offsets always refer to "virtual frame offsets".//// For x64 this falls out because Tier0 frames are always FP frames, and so the FP-relative// offset is what we want.//// For arm64, if the frame pointer is not at the top of the frame, we need to adjust the// offset.#if defined(TARGET_AMD64)// We add +TARGET_POINTER_SIZE here is to account for the slot that Jit_Patchpoint// creates when it simulates calling the OSR method (the "pseudo return address" slot).// This is effectively a new slot at the bottom of the Tier0 frame.//constint totalFrameSize = codeGen->genTotalFrameSize() + TARGET_POINTER_SIZE;constint offsetAdjust = 0;#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)// SP is not manipulated by calls so no frame size adjustment needed.// Local Offsets may need adjusting, if FP is at bottom of frame.//constint totalFrameSize = codeGen->genTotalFrameSize();constint offsetAdjust = codeGen->genSPtoFPdelta() - totalFrameSize;#elseNYI("patchpoint info generation");constint offsetAdjust = 0;constint totalFrameSize = 0;#endifpatchpointInfo->Initialize(info.compLocalsCount, totalFrameSize);JITDUMP("--OSR--- Total Frame Size %d, local offset adjust is %d\n", patchpointInfo->TotalFrameSize(),offsetAdjust);// We record offsets for all the "locals" here. Could restrict// this to just the IL locals with some extra logic, and save a bit of space,// but would need to adjust all consumers, too.for (unsigned lclNum = 0; lclNum < info.compLocalsCount; lclNum++){// If there are shadowed params, the patchpoint info should refer to the shadow copy.//unsigned varNum = lclNum;if (gsShadowVarInfo != nullptr){unsignedconst shadowNum = gsShadowVarInfo[lclNum].shadowCopy;if (shadowNum != BAD_VAR_NUM){assert(shadowNum < lvaCount);assert(shadowNum >= info.compLocalsCount);varNum = shadowNum;}}LclVarDsc* const varDsc = lvaGetDesc(varNum);// We expect all these to have stack homes, and be FP relativeassert(varDsc->lvOnFrame);assert(varDsc->lvFramePointerBased);// Record FramePtr relative offset (no localloc yet)// Note if IL stream contained an address-of that potentially leads to exposure.// That bit of IL might be skipped by OSR partial importation.constbool isExposed = varDsc->lvHasLdAddrOp;patchpointInfo->SetOffsetAndExposure(lclNum, varDsc->GetStackOffset() + offsetAdjust, isExposed);JITDUMP("--OSR-- V%02u is at virtual offset %d%s%s\n", lclNum, patchpointInfo->Offset(lclNum),patchpointInfo->IsExposed(lclNum) ? " (exposed)" : "", (varNum != lclNum) ? " (shadowed)" : "");}// Special offsets//if (lvaReportParamTypeArg()){constint offset = lvaCachedGenericContextArgOffset();patchpointInfo->SetGenericContextArgOffset(offset + offsetAdjust);JITDUMP("--OSR-- cached generic context virtual offset is %d\n", patchpointInfo->GenericContextArgOffset());}if (lvaKeepAliveAndReportThis()){constint offset = lvaCachedGenericContextArgOffset();patchpointInfo->SetKeptAliveThisOffset(offset + offsetAdjust);JITDUMP("--OSR-- kept-alive this virtual offset is %d\n", patchpointInfo->KeptAliveThisOffset());}if (compGSReorderStackLayout){assert(lvaGSSecurityCookie != BAD_VAR_NUM);LclVarDsc* const varDsc = lvaGetDesc(lvaGSSecurityCookie);patchpointInfo->SetSecurityCookieOffset(varDsc->GetStackOffset() + offsetAdjust);JITDUMP("--OSR-- security cookie V%02u virtual offset is %d\n", lvaGSSecurityCookie,patchpointInfo->SecurityCookieOffset());}if (lvaMonAcquired != BAD_VAR_NUM){LclVarDsc* const varDsc = lvaGetDesc(lvaMonAcquired);patchpointInfo->SetMonitorAcquiredOffset(varDsc->GetStackOffset() + offsetAdjust);JITDUMP("--OSR-- monitor acquired V%02u virtual offset is %d\n", lvaMonAcquired,patchpointInfo->MonitorAcquiredOffset());}#if defined(TARGET_AMD64)// Record callee save registers.// Currently only needed for x64.//regMaskTP rsPushRegs = codeGen->regSet.rsGetModifiedCalleeSavedRegsMask();rsPushRegs |= RBM_FPBASE;patchpointInfo->SetCalleeSaveRegisters((uint64_t)rsPushRegs);JITDUMP("--OSR-- Tier0 callee saves: ");JITDUMPEXEC(dspRegMask((regMaskTP)patchpointInfo->CalleeSaveRegisters()));JITDUMP("\n");#endif// Register this with the runtime.info.compCompHnd->setPatchpointInfo(patchpointInfo);}
wokers&pages->你的网站名称->settings->Domains & Routes->adddomains->overview->域名->dns->records->可以看到其规则情况攻击目标:┌─────────────────┐│ 返回地址 │ ← 覆盖这里 → 劫持程序执行流├─────────────────┤│ 参数/变量 │ ← 覆盖这里 → 篡改程序逻辑└─────────────────┘Canary → 防止返回地址被覆盖Shadow → 防止重要参数/变量被覆盖GS Cookie = Canary = Security CookieShadow是GS编译的一部分高地址┌──────────────────┐│ param_shadow │ ← Shadow Copy 保护这里├──────────────────┤ 编译器只用shadow,original被覆盖没关系│ saved RBP │├──────────────────┤│ 返回地址 │ ← Canary 保护这里├──────────────────┤│ Canary │ ← 哨兵,溢出必须先经过它├──────────────────┤│ param_original │ ← 牺牲品,被覆盖也无所谓├──────────────────┤│ buffer[16] ││ buffer[0] │ ← 溢出起点└──────────────────┘低地址
夜雨聆风