乐于分享
好东西不私藏

探究OpenClaw+CPython解释器+OSR堆栈替换(net10)

探究OpenClaw+CPython解释器+OSR堆栈替换(net10)
批判性探究OpenClaw
从头开始。
OpenClaw启动的时候一般都是运行如下命令,启动了整个龙虾。
OpenClaw GateWay
这个命令是如何通过代码运行的呢?OpenClaw是一个二进制文件,而它实际上指向的一个软链接文件openclaw.mjs。这个文件才是龙虾运行的第一个文件。
tang@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
这里说点题外话,有一个package.json文件。它在npm install的时候会读取里面的bin字段,把openclaw.mjs创建为openclaw二进制的软链接。然后就形成了以上过程。
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": {
openclaw.mjs里面调用了entry.ts这个是程序的入口,OpenClaw本身版本号检查在这里。这里import version.ts。可以理解VERSION是这个version.js里面的一个常量。
// home/tang/opt/openclawsource/openclaw/src/entry.ts function tryHandleRootVersionFastPath(argv: string[]): boolean {    if (!isRootVersionInvocation(argv)) {      return false;    }    import("./version.js")      .then(({ VERSION }) => {        console.log(VERSION);      })
以及调用了runCli(类似于C的main)
 if (!tryHandleRootVersionFastPath(process.argv) && !tryHandleRootHelpFastPath(process.argv)) {      import("./cli/run-main.js")        .then(({ runCli }) => runCli(process.argv))
runcli里面OpenClaw检查nodejs版本是否大于22
// /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);}
比如说我们安装了QQ插件,这个runCli此时就会启动QQ机器人(Starting gateway),初始化账号(startAccount)
// 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
进行一个websocket链接
// vim /home/tang/.openclaw/extensions/openclaw-qqbot/src/gateway.ts    ws.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 },        });      });
如此OpenClaw就跟QQ建立了联系。以后QQ发送消息,都有这个插件来接受。如果调用AI的话还是OpenClaw,具体的就是QQ插件把QQ发送过来的消息交给OpenClaw,OpenClaw调用AI大模型返回消息,最后把消息发送给QQ。
// vim /home/tang/.openclaw/extensions/openclaw-qqbot/src/gateway.tsconst dispatchPromise = pluginRuntime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({ctx: ctxPayload,
这里其实还有个问题。QQ插件注册的问题,它是怎么融入OpenClaw的?
OpenClaw启动加载插件,会查看注册,比如QQ插件
$ 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发现有注册的QQ插件,
startGateway函数会调用QQ API建立联系
  // ~/.openclaw/extensions/openclaw-qqbot/src/channel.ts  await 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,          });        },      });
startGateWay实现在
// ~/.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}`);
从你输入一个消息(动作)开始,到返回。OpenClaw历经了什么,本篇看下。任何动作都可以看做一个任务,所以我们先从任务开始。
OpenClaw函数drainLane会检查并发槽里的任务是否为空,如果是则执行任务。
// 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();  //调用任务}
注意这里的调用任务(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);                    }                })();
entry.task()任务会进入以下函数
/*runEmbeddedAttempt(单次尝试)        ├── 解析工作目录        ├── 初始化沙箱        ├── 加载 skills        ├── 加载 bootstrap 文件        ├── 创建工具(createOpenClawCodingTools)        ├── buildEmbeddedSystemPrompt(构建 system prompt)        └── 交给 pi-agent-core runLoop 驱动模型*/// src/agents/pi-embedded-runner/run.ts        async function runEmbeddedPiAgent(params) {        const attempt = await runEmbeddedAttempt({sessionId: params.sessionId,sessionKey: params.sessionKey...        }
runEmbeddedAttempt里面会调用
buildEmbeddedSystemPrompt构建提示词。同时它也会调用工具

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--》
整个OpenClaw的QQ插件运行过程梳理完毕,大致是OpenClaw启动,然后QQ初始化,连接等待消息,接受消息转给OpenClaw让其大模型处理,然后返回给QQ的一个过程。
我们可以模拟下QQ机器人和OpenClaw对话过程,代码大约100多行,小巧精悍。
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 os    from openai import OpenAI    client = 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 = None            async 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"] / 1000                    asyncio.create_task(heartbeat(ws, heartbeat_interval))                    # 发送 Identify                    await 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: 心跳 ACK                elif 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())
主要是通过QQ发送消息,这段程序接收到了以后,转发给deepseek Agent,把agent的结果发送给QQ,完成整个对话。
如果加上操控电脑
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 os    from openai import OpenAI    client = 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    )    # print    msg = response.choices[0].message    if msg.tool_calls:        messages.append(msg)        for tool_call in msg.tool_calls:            func_name = tool_call.function.name            func_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 = None            async 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"] / 1000                    asyncio.create_task(heartbeat(ws, heartbeat_interval))                    # 发送 Identify                    await 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: 心跳 ACK                elif 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())
这样的话就可以通过工具,来操控电脑,比如打印当前目录的文件。打开CMD等等。
以上两百多行代码,简单的openClaw实现过程。
python解释器
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-pydebug make -j$(nproc) make install
hello.py
x = 1 + 2print(x)
debug
gdb $HOME/opt/python-debug/bin/python3 set 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                   0  1           LOAD_SMALL_INT           3              STORE_NAME               0 (x)  2           LOAD_NAME                1 (print)              PUSH_NULL              LOAD_NAME                0 (x)              CALL                     1              POP_TOP              LOAD_CONST               1 (None)              RETURN_VALUERESUMELOAD_NAMELOAD_GLOBAL  offset=  0  opcode=128 (RESUME                        )  arg=0  offset=  2  opcode=  0 (CACHE                         )  arg=0  offset=  4  opcode= 93 (LOAD_NAME                     )  arg=3  offset=  6  opcode=115 (STORE_GLOBAL                  )  arg=0  offset=  8  opcode= 92 (LOAD_GLOBAL                   )  arg=1  offset= 10  opcode= 31 (POP_TOP                       )  arg=0  offset= 12  opcode= 92 (LOAD_GLOBAL                   )  arg=0  offset= 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     弹栈存入 x  8    0x5c 0x01      LOAD_NAME               1     压栈 print 10    0x1f 0x00      PUSH_NULL               0     压入 NULL(调用约定) 12    0x5c 0x00      LOAD_NAME               0     压栈 x 14    0x32 0x01      CALL                    1     调用,参数个数=1  …    0x00 ...       CACHE×4                       CALL 后跟4个缓存槽       0x1d 0x00      POP_TOP                 0     丢弃 print() 返回值       0x51 0x01      LOAD_CONST              1     压栈 None       0x21 0x00      RETURN_VALUE            0     返回
细节
x = 1 + 2```**GDB 里看不到加法指令**,因为编译器在 `_PyCfg_OptimizeCodeUnit` 阶段做了**常量折叠**:```优化前:                        优化后:  LOAD_SMALL_INT  1               LOAD_SMALL_INT  3   ← 直接是3  LOAD_SMALL_INT  2               STORE_NAME      0  BINARY_OP       +  STORE_NAME      0
何时编译成机器码,解释器不会编译机器码,只会进行解释执行。JIT则会,新版的python可以,这里不介绍。
解释知悉如下:
//home/tang/opt/cpython/cpython/Python/generated_cases.c.h 比如把整数3压入栈TARGET(LOAD_SMALL_INT) {            #if _Py_TAIL_CALL_INTERP            int opcode = LOAD_SMALL_INT;            (void)(opcode);            #endif            frame->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();        }
dispatch则继续下一个指令执行执行,以完成当前函数。
OSR堆栈替换(net10)
前言
osr堆栈替换大约前面几个版本就引进了,这里依旧看下新版(net10)的实现。
详情
benchmark官例
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:*'
我们如果通过clr debug也需要release版本的托管dll,且设置以下环境变量
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
Linux下面,需要设置环境变量
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-sos全局,再install(不要忘记install,否则lldb不识别)
dotnet tool install --global dotnet-sosdotnet-sos install
install之后lldb才算成功
vim ~/.lldbinit#START - ADDED BY SOS INSTALLERplugin load /home/tang/.dotnet/sos/libsosplugin.sosetsymbolserver -ms#END - ADDED BY SOS INSTALLER
以下例子为说明测试,注意放入到coreclr\windows.x64.Debug路径必须是托管dll的release才能看到结果
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; }
在clr导入IL的时候,会判断当前这个il函数是否适合启用osr。
先看jit日志,导入到txt里面是便于搜索观看,比如关键字:osr,patch等。
$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 key jit日志,就表示当前方法适合使用osr。
OSR enabled forthis method
判断其是否适合使用方法,这里大约是,如果方法里有:方法里有local(在栈上分配动态大小内存),表示异常处理块里有向前跳转(OSR 不支持在 handler 内部循环 → 不安全 → 不能生成 patchpoint),托管代码被 native 调用(OSR 不能在 reverse P/Invoke 情况下插入 patchpoint → 因为栈布局可能不符合预期),方法是实例方法,并且 this 可修改(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";    }#else    whyNot = "OSR feature not defined in build";#endifif (reason != nullptr)    {        *reason = whyNot;    }return whyNot == nullptr;}
这是第一步。
因JIT编译的时候把IL分成一个个BB块,JIT会判断osr放到哪个块合适。比如
Can't set source patchpoint at BB10, using target BB02 instead
代码
if (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);}
下面就是把osr插入到可以放入的bb块
*************** Starting PHASE Expand patchpointsPatchpoint: regular patchpoint in BB02
日志,这里主要看TransformBlock
// 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 up        if (ppCounterLclNum == BAD_VAR_NUM)        {            ppCounterLclNum                            = compiler->lvaGrabTemp(true DEBUGARG("patchpoint counter"));            compiler->lvaTable[ppCounterLclNum].lvType = TYP_INT;            // and initialize in the entry block            TransformEntry(compiler->fgFirstBB);        }        // Capture the IL offset        IL_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 flags        block->SetFlags(BBF_INTERNAL);        helperBlock->SetFlags(BBF_BACKWARD_JUMP);        //这个断言确保block 块的目标是remainderblock 块,这里目标target         assert(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 块的前置块是helperblock        FlowEdge* const newEdge = compiler->fgAddRefPred(remainderBlock, helperBlock);        //再设置helperblock的目标是remainderblock,这样 remainderblock就有两条支线到达了          helperBlock->SetTargetEdge(newEdge);        // Update weights        remainderBlock->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的结果写回ppCounterLclNum        GenTree* ppCounterUpdate = compiler->gtNewStoreLclVarNode(ppCounterLclNum, ppCounterSub);        // 把这棵树插入到block块的末尾。,下面所有都是以此类推         compiler->fgNewStmtAtEnd(block, ppCounterUpdate);        // if (ppCounter > 0), bypass helper call        GenTree* 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);    }
这段代码主要是构建了以下三行IL
--ppCounter;if (ppCounter > 0), bypass helper call call PPHelper(&ppCounter, ilOffset) 代码原理 : 1.先分割block为两个block(block和remainderblock): fgSplitBlockAtBeginning   注意这里的分割之后,block的目标(target)依旧是remainderblock,这点可以通过断言看出来assert(block->TargetIs(remainderBlock)) 2.新建一个block(helperblock):CreateAndInsertBasicBlock 3.把helperblock插入到block和remainderblock的中间,进行了分支预测。trueedge和falsedge 4.像这种:gtNewLclvNode表示新建一个本地(Lcl)变量(v)节点(Node)   gtNewIconNode新建一个常量(Icon)节点(Node)
那么代码块插入了之后,后面会进行一个调整,最终插入到bb12块
***** 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}
此时就完成了,类似于这种的asm
G_M000_IG04:                ;; offset=0x0059lea      rcx, [rbp-0x68]mov      edx, 6call     CORINFO_HELP_PATCHPOINT
OK,以上是JIT布局生成的BB块以及asm。
这种布局完成之后何时运行呢?
这里我们可以把托管的DLL编译成release模式,放入到runtime目录调试。
主要做了以下事情:
1.环境变量判断hitLimit,hitCount
2.回溯,
RtlCaptureContext获取当前所在帧栈状态的contextVirtualUnwindToFirstManagedCallFrame找到当前调用堆栈的第一个托管帧栈(这里是main),如果中间有非托管直接跳过CallDescrWorker获取调用当前函数的函数帧栈,也即是main的上一层(CallDescrWorkerInternalReturnAddress)
3.osr取代原有的函数头入口以及执行
// 取代SetIP(pFrameContext, osrMethodCode)// 执行ClrRestoreNonvolatileContext(pFrameContext);
我们先看JIT_Patchpoint
NESTED_ENTRY JIT_Patchpoint, _TEXT        PROLOG_WITH_TRANSITION_BLOCK        lea     rcx, [rsp + __PWTB_TransitionBlock] ; TransitionBlock *        call    JIT_PatchpointWorkerWorkerWithPolicy        EPILOG_WITH_TRANSITION_BLOCK_RETURN        TAILJMP_RAXNESTED_END JIT_Patchpoint, _TEXT
JIT_PatchpointWorkerWorkerWithPolicy,这个函数里面进行了三次回溯。
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);   // 回溯到当前堆栈第一次托管函数调用的帧栈,也即是main   pThread->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);}
注意上面的PatchpointOptimizationPolicy函数,它里面有些环境变量
// 这个是设置程序循环或者运行了多少次才会触发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。
下是clr里面获取osr的DebugInfo信息,用于运行时获取信息用。
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;#endif    patchpointInfo->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);}
以上就是OSR的整个过程。
杂项
cloudflare自定义域名
wokers&pages->你的网站名称->settings->Domains & Routes->add
dns规则
domains->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]       │  ← 溢出起点└──────────────────┘低地址
结尾
本篇三部分,分别是openclaw/cpython/os源码级别分析

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料