收起左侧

在飞牛OS部署MCP服务拉取系统日志

0
回复
89
查看
[ 复制链接 ]

17

主题

195

回帖

355

牛值

社区共建团

社区共建团荣誉勋章飞牛百度网盘玩家fnOS1.0上线纪念勋章

前言

最近 AI 再次成为热点,而 MCP 的出现在一定程度上弥补了大模型与外部系统交互能力的不足。
借助 MCP,模型不再只是生成文本,而是可以直接调用外部服务,从而真正参与到实际系统的操作与管理中。

本文通过一个简单示例,演示如何部署一个 MCP 服务,并为 AI 提供获取飞牛系统日志的能力。

服务代码

被折叠的代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
trimrpc MCP Server — fnOS 系统管理 MCP 工具服务
================================================

通过 Streamable HTTP 传输提供 fnOS 系统管理能力的 MCP 工具服务器。
内置 trimrpc 二进制协议栈,通过 Unix Domain Socket 与本机 fnOS
RPC 服务通信。零外部依赖,仅使用 Python 标准库。

可用工具:
  get_system_logs    获取系统事件日志 (登录、配置变更、SSH 等)
  trimrpc_call       通用 trimrpc RPC 调用 (任意服务/方法)
  discover_services  发现可用的 RPC 服务

用法:
  python3 report-trimrpc-mcp-202603062124.py                  # 默认 0.0.0.0:9800
  python3 report-trimrpc-mcp-202603062124.py --port 8080      # 自定义端口
  python3 report-trimrpc-mcp-202603062124.py --host 127.0.0.1 # 仅本机
  python3 report-trimrpc-mcp-202603062124.py --list-tools     # 列出工具
  python3 report-trimrpc-mcp-202603062124.py -v               # 调试日志

MCP 客户端配置 (Claude Code settings / .mcp.json):
  {
    "mcpServers": {
      "fnos": {
        "url": "http://<fnOS-IP>:9800/mcp"
      }
    }
  }
"""

import sys
import json
import struct
import socket
import os
import time
import uuid
import threading
import logging
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn

# ═══════════════════════════════════════════════════════════════════
# 日志
# ═══════════════════════════════════════════════════════════════════

logging.basicConfig(
    stream=sys.stderr,
    level=logging.INFO,
    format='[trimrpc-mcp] %(levelname)s %(message)s',
)
log = logging.getLogger('trimrpc-mcp')


# ═══════════════════════════════════════════════════════════════════
# trimrpc 二进制协议核心
#
# 协议格式: 30 字节固定头部 + 4 段变长数据
# 传输层:   Unix Domain Socket (AF_UNIX, SOCK_STREAM)
# ═══════════════════════════════════════════════════════════════════

CPRT_MAGIC       = 0x54525043       # "CPRT" little-endian
PROTOCOL_VERSION = 1
HEADER_SIZE      = 30

BROKER_SERVICE   = "com.trim.rpcbroker"
BROKER_UDS_PATHS = [
    "/run/trim_app_cgi/rpcbroker",
]
DEFAULT_TIMEOUT      = 10
SERVICE_PREFIX       = "com."
MAX_SERVICE_NAME_LEN = 128

_sid_counter = 0
_sid_lock    = threading.Lock()


def _next_session_id():
    global _sid_counter
    with _sid_lock:
        _sid_counter += 1
        return _sid_counter


class RpcPacket:
    """
    trimrpc 二进制数据包。

    头部 (30 字节, little-endian):
      magic(4) + version(2) + session_id(8) +
      data_len(2) + token_len(2) +
      extra_data_len(4) + reserved(4) + extra_data2_len(4)

    数据段: [data] + [token] + [extra_data(JSON 载荷)] + [extra_data2]
    """
    HEADER_FMT = '<IHQHHIII'

    def __init__(self):
        self.magic = CPRT_MAGIC
        self.version = PROTOCOL_VERSION
        self.session_id = 0
        self.data = b''          # Section 0: 调用方 UUID
        self.token = b''         # Section 1: 认证令牌
        self.extra_data = b''    # Section 2: JSON 载荷
        self.extra_data2 = b''   # Section 3: 扩展数据

    def serialize(self):
        header = struct.pack(
            self.HEADER_FMT,
            self.magic, self.version, self.session_id,
            len(self.data), len(self.token),
            len(self.extra_data), 0, len(self.extra_data2),
        )
        return header + self.data + self.token + self.extra_data + self.extra_data2

    @classmethod
    def deserialize(cls, raw):
        if len(raw) < HEADER_SIZE:
            raise ValueError(f"数据不足: 需要 {HEADER_SIZE} 字节, 实际 {len(raw)}")
        pkt = cls()
        (
            pkt.magic, pkt.version, pkt.session_id,
            d_len, t_len, e_len, _, e2_len,
        ) = struct.unpack(cls.HEADER_FMT, raw[:HEADER_SIZE])
        if pkt.magic != CPRT_MAGIC:
            raise ValueError(f"协议魔数不匹配: 0x{pkt.magic:08X}")
        off = HEADER_SIZE
        pkt.data        = raw[off:off + d_len];  off += d_len
        pkt.token       = raw[off:off + t_len];  off += t_len
        pkt.extra_data  = raw[off:off + e_len];  off += e_len
        pkt.extra_data2 = raw[off:off + e2_len]
        return pkt

    def get_json_payload(self):
        if self.extra_data:
            return self.extra_data.decode('utf-8', errors='replace')
        if self.data:
            return self.data.decode('utf-8', errors='replace')
        return ''


def _recv_all(sock, size, timeout=DEFAULT_TIMEOUT):
    sock.settimeout(timeout)
    buf = b''
    while len(buf) < size:
        try:
            chunk = sock.recv(size - len(buf))
        except socket.timeout:
            raise TimeoutError(f"接收超时 ({timeout}s): {len(buf)}/{size} 字节")
        if not chunk:
            raise ConnectionError(f"连接断开: {len(buf)}/{size} 字节")
        buf += chunk
    return buf


def send_rpc_packet(uds_path, packet, timeout=DEFAULT_TIMEOUT):
    """通过 Unix Domain Socket 发送 RpcPacket 并接收响应"""
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    try:
        sock.connect(uds_path)
        sock.sendall(packet.serialize())
        resp_header = _recv_all(sock, HEADER_SIZE, timeout)
        (
            _, _, _, d_len, t_len, e_len, _, e2_len,
        ) = struct.unpack(RpcPacket.HEADER_FMT, resp_header)
        total = d_len + t_len + e_len + e2_len
        resp_body = _recv_all(sock, total, timeout) if total > 0 else b''
        return RpcPacket.deserialize(resp_header + resp_body)
    except FileNotFoundError:
        raise ConnectionError(f"UDS 不存在: {uds_path} (RPC 服务可能未运行)")
    except PermissionError:
        raise ConnectionError(f"无权限连接: {uds_path}")
    finally:
        sock.close()


def build_reqid():
    ts = int(time.time() * 1000000)
    return format(ts & 0xFFFFFFFFFFFFFFFF, '016x')


def build_rpc_request(service, method, params=None, caller_uuid=None):
    if len(service) > MAX_SERVICE_NAME_LEN:
        raise ValueError(f"服务名超过 {MAX_SERVICE_NAME_LEN} 字节")
    if not service.startswith(SERVICE_PREFIX):
        raise ValueError(f'服务名必须以 "{SERVICE_PREFIX}" 开头')

    json_payload = {
        "data": {
            "req": f"{service}.{method}",
            "pid": os.getpid(),
            "reqid": build_reqid(),
        }
    }
    if params:
        json_payload["data"].update(params)

    pkt = RpcPacket()
    pkt.session_id = _next_session_id()
    pkt.data = (caller_uuid or str(uuid.uuid4()).upper()).encode('utf-8')
    pkt.extra_data = json.dumps(json_payload, separators=(',', ':')).encode('utf-8')
    return pkt


class RpcClient:
    """trimrpc RPC 客户端 — 自动服务发现与调用"""

    def __init__(self, caller_uuid=None, caller_service="com.trim.pyclient",
                 timeout=DEFAULT_TIMEOUT, broker_uds=None):
        self.uuid = caller_uuid or str(uuid.uuid4()).upper()
        self.caller_service = caller_service
        self.timeout = timeout
        self.broker_uds = broker_uds or self._find_broker()
        self.services = {
            BROKER_SERVICE: {"name": BROKER_SERVICE, "uds": self.broker_uds},
        }

    def _find_broker(self):
        for p in BROKER_UDS_PATHS:
            if os.path.exists(p):
                return p
        return BROKER_UDS_PATHS[0]

    def apply_permission(self, target_services):
        if isinstance(target_services, str):
            target_services = [target_services]
        pkt = build_rpc_request(
            BROKER_SERVICE, "apply",
            {"services": target_services},
            caller_uuid=self.uuid,
        )
        resp_pkt = send_rpc_packet(self.broker_uds, pkt, self.timeout)
        resp_str = resp_pkt.get_json_payload()
        if not resp_str:
            raise RuntimeError("Broker 返回空响应")
        resp = json.loads(resp_str)
        resp_data = resp.get("data", resp)
        if resp_data.get("result") != "succ":
            raise RuntimeError(
                f"ApplyPermission 失败: {json.dumps(resp_data, ensure_ascii=False)}"
            )
        for svc in resp_data.get("data", []):
            sid = svc.get("id", "")
            if sid:
                self.services[sid] = svc
        return resp_data.get("data", [])

    def call(self, service, method, params=None):
        svc_info = self.services.get(service)
        if not svc_info:
            self.apply_permission([service])
            svc_info = self.services.get(service)
            if not svc_info:
                raise RuntimeError(f"服务未找到: {service}")
        uds_path = svc_info.get("uds", "")
        if not uds_path:
            raise RuntimeError(f"服务 {service} 无 UDS 路径")
        pkt = build_rpc_request(service, method, params, caller_uuid=self.uuid)
        token = svc_info.get("token", "")
        if token:
            pkt.token = token.encode('utf-8')
        resp_pkt = send_rpc_packet(uds_path, pkt, self.timeout)
        resp_str = resp_pkt.get_json_payload()
        return json.loads(resp_str) if resp_str else {}


# ═══════════════════════════════════════════════════════════════════
# MCP 工具定义与实现
# ═══════════════════════════════════════════════════════════════════

MCP_TOOLS = [
    {
        "name": "get_system_logs",
        "description": (
            "获取 fnOS 系统事件日志。返回系统操作记录,包括用户登录、"
            "系统配置变更、SSH 连接等事件。支持分页和按模块筛选。"
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "page": {
                    "type": "integer",
                    "description": "页码,从 1 开始",
                    "default": 1,
                },
                "pageSize": {
                    "type": "integer",
                    "description": "每页条数,范围 1-100",
                    "default": 20,
                },
                "module": {
                    "type": "integer",
                    "description": "模块筛选: -1 表示全部模块",
                    "default": -1,
                },
            },
        },
    },
    {
        "name": "trimrpc_call",
        "description": (
            "通用 fnOS trimrpc RPC 调用。通过 RPC Broker 调用 fnOS 上的"
            "任意已注册 RPC 服务。服务名必须以 'com.' 开头。"
            "示例: service='com.trim.storage', method='get_info'"
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "service": {
                    "type": "string",
                    "description": "目标服务名,如 'com.trim.storage'",
                },
                "method": {
                    "type": "string",
                    "description": "方法名,如 'get_info' 或 'common.list'",
                },
                "params": {
                    "type": "object",
                    "description": "调用参数",
                    "default": {},
                },
            },
            "required": ["service", "method"],
        },
    },
    {
        "name": "discover_services",
        "description": (
            "发现 fnOS 上可用的 RPC 服务。查询 RPC Broker 获取服务路由信息,"
            "包括 UDS 路径、IP、类型、认证令牌等。"
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "services": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": (
                        "要查询的服务名列表。"
                        "示例: ['com.trim.storage', 'com.trim.main']"
                    ),
                    "default": [],
                },
            },
        },
    },
]

# ── 全局 RPC 客户端 ──

_rpc_client = None
_rpc_client_lock = threading.Lock()
_broker_uds_override = None  # 由 --broker-uds 设置


def _get_client():
    global _rpc_client
    with _rpc_client_lock:
        if _rpc_client is None:
            _rpc_client = RpcClient(broker_uds=_broker_uds_override)
            log.info(f"RPC 客户端初始化, Broker: {_rpc_client.broker_uds}")
        return _rpc_client


# ── 工具处理函数 ──

def tool_get_system_logs(arguments):
    page      = max(1, arguments.get("page", 1))
    page_size = max(1, min(100, arguments.get("pageSize", 20)))
    module    = arguments.get("module", -1)

    result = _get_client().call(
        "com.trim.eventlogger",
        "common.list",
        {"pageSize": page_size, "page": page, "module": module},
    )
    return json.dumps(result, indent=2, ensure_ascii=False)


def tool_trimrpc_call(arguments):
    service = arguments.get("service", "")
    method  = arguments.get("method", "")
    params  = arguments.get("params", {})
    if not service:
        raise ValueError("缺少必填参数 'service'")
    if not method:
        raise ValueError("缺少必填参数 'method'")

    result = _get_client().call(service, method, params if params else None)
    return json.dumps(result, indent=2, ensure_ascii=False)


def tool_discover_services(arguments):
    services = arguments.get("services", [])
    client = _get_client()

    if not services:
        try:
            pkt = build_rpc_request(
                BROKER_SERVICE, "list", {}, caller_uuid=client.uuid,
            )
            resp_pkt = send_rpc_packet(client.broker_uds, pkt, client.timeout)
            resp_str = resp_pkt.get_json_payload()
            if resp_str:
                return json.dumps(json.loads(resp_str), indent=2, ensure_ascii=False)
        except Exception:
            pass
        services = [BROKER_SERVICE]

    result = client.apply_permission(services)
    return json.dumps(result, indent=2, ensure_ascii=False)


TOOL_HANDLERS = {
    "get_system_logs":   tool_get_system_logs,
    "trimrpc_call":      tool_trimrpc_call,
    "discover_services": tool_discover_services,
}


# ═══════════════════════════════════════════════════════════════════
# MCP JSON-RPC 处理
# ═══════════════════════════════════════════════════════════════════

MCP_PROTOCOL_VERSION = "2024-11-05"
SERVER_INFO = {"name": "trimrpc-mcp", "version": "1.0.0"}


def process_jsonrpc(msg):
    """
    处理一条 JSON-RPC 2.0 消息,返回响应 dict 或 None (通知无需响应)。
    """
    msg_id = msg.get("id")
    method = msg.get("method", "")
    params = msg.get("params") or {}

    # 通知 (无 id) 不需要回复
    if msg_id is None:
        log.debug(f"通知: {method}")
        return None

    def _result(result):
        return {"jsonrpc": "2.0", "id": msg_id, "result": result}

    def _error(code, message):
        return {"jsonrpc": "2.0", "id": msg_id, "error": {"code": code, "message": message}}

    if method == "initialize":
        return _result({
            "protocolVersion": MCP_PROTOCOL_VERSION,
            "capabilities": {"tools": {}},
            "serverInfo": SERVER_INFO,
        })

    if method == "ping":
        return _result({})

    if method == "tools/list":
        return _result({"tools": MCP_TOOLS})

    if method == "tools/call":
        tool_name = params.get("name", "")
        arguments = params.get("arguments", {})
        handler = TOOL_HANDLERS.get(tool_name)
        if not handler:
            return _error(-32602, f"未知工具: {tool_name}")
        try:
            text = handler(arguments)
            return _result({"content": [{"type": "text", "text": text}]})
        except Exception as e:
            log.error(f"工具 {tool_name} 执行失败: {e}")
            return _result({
                "content": [{"type": "text", "text": f"错误: {e}"}],
                "isError": True,
            })

    return _error(-32601, f"方法不存在: {method}")


# ═══════════════════════════════════════════════════════════════════
# HTTP 传输层 (Streamable HTTP)
#
# POST /mcp → 发送 JSON-RPC 请求, 直接返回 JSON 响应
# ═══════════════════════════════════════════════════════════════════


class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True
    allow_reuse_address = True


class McpRequestHandler(BaseHTTPRequestHandler):

    def lo**essage(self, fmt, *args):
        log.debug(f"{self.client_address[0]} {fmt % args}")

    def _cors_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS')
        self.send_header('Access-Control-Allow-Headers', 'Content-Type')

    def _json_response(self, code, obj):
        body = json.dumps(obj, ensure_ascii=False).encode('utf-8')
        self.send_response(code)
        self.send_header('Content-Type', 'application/json')
        self.send_header('Content-Length', str(len(body)))
        self._cors_headers()
        self.end_headers()
        self.wfile.write(body)

    def do_OPTIONS(self):
        self.send_response(200)
        self._cors_headers()
        self.end_headers()

    def do_DELETE(self):
        self.send_response(200)
        self._cors_headers()
        self.end_headers()

    def do_GET(self):
        self._json_response(200, {
            "name": SERVER_INFO["name"],
            "version": SERVER_INFO["version"],
            "status": "running",
            "tools": [t["name"] for t in MCP_TOOLS],
        })

    def do_POST(self):
        content_len = int(self.headers.get('Content-Length', 0))
        if content_len == 0:
            self._json_response(400, {"error": "Empty body"})
            return

        try:
            body = self.rfile.read(content_len)
            msg = json.loads(body)
        except (json.JSONDecodeError, UnicodeDecodeError) as e:
            self._json_response(400, {"error": f"Invalid JSON: {e}"})
            return

        log.debug(f"← {msg.get('method', '?')} (id={msg.get('id')})")
        response = process_jsonrpc(msg)
        if response is not None:
            self._json_response(200, response)
        else:
            self.send_response(202)
            self._cors_headers()
            self.end_headers()


# ═══════════════════════════════════════════════════════════════════
# 入口
# ═══════════════════════════════════════════════════════════════════

def main():
    import argparse
    parser = argparse.ArgumentParser(
        description="trimrpc MCP Server — fnOS 系统管理工具",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  %(prog)s                          # 默认 0.0.0.0:9800
  %(prog)s --port 8080              # 自定义端口
  %(prog)s --host 127.0.0.1         # 仅允许本机访问
  %(prog)s --broker-uds /tmp/rpc/rpcbroker

MCP 配置:
  {"mcpServers": {"fnos": {"url": "http://<IP>:9800/mcp"}}}
""",
    )
    parser.add_argument('--host', default='0.0.0.0', help='监听地址 (默认: 0.0.0.0)')
    parser.add_argument('--port', type=int, default=9800, help='监听端口 (默认: 9800)')
    parser.add_argument('--broker-uds', default=None, help='指定 RPC Broker UDS 路径')
    parser.add_argument('--list-tools', action='store_true', help='列出可用工具并退出')
    parser.add_argument('-v', '--verbose', action='store_true', help='调试日志')
    args = parser.parse_args()

    if args.verbose:
        logging.getLogger().setLevel(logging.DEBUG)

    if args.list_tools:
        print("trimrpc MCP Server 可用工具:\n")
        for t in MCP_TOOLS:
            print(f"  {t['name']}")
            print(f"    {t['description']}")
            schema = t.get("inputSchema", {})
            props = schema.get("properties", {})
            if props:
                print("    参数:")
                for k, v in props.items():
                    req = "必填" if k in schema.get("required", []) else "可选"
                    desc = v.get("description", "")
                    default = v.get("default")
                    default_str = f", 默认={default}" if default is not None else ""
                    print(f"      {k} ({req}{default_str}): {desc}")
            print()
        return

    global _broker_uds_override
    _broker_uds_override = args.broker_uds

    server = ThreadedHTTPServer((args.host, args.port), McpRequestHandler)
    host_display = args.host if args.host != '0.0.0.0' else '<IP>'
    print(f"trimrpc MCP Server 已启动")
    print(f"  监听: http://{args.host}:{args.port}")
    print(f"  端点: POST http://{host_display}:{args.port}/mcp")
    print(f"  工具: {', '.join(t['name'] for t in MCP_TOOLS)}")
    if args.broker_uds:
        print(f"  Broker: {args.broker_uds}")
    print(f'\n  MCP 配置: {{"mcpServers": {{"fnos": {{"url": "http://{host_display}:{args.port}/mcp"}}}}}}')
    print()

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n正在关闭...")
        server.shutdown()


if __name__ == '__main__':
    main()

部署

将代码复制到任意可执行目录中,这里文件名为:

report-trimrpc-mcp-202603062124.py

直接运行即可:

Linux fnos 6.12.18-trim #211 SMP PREEMPT_DYNAMIC Fri Jan 30 17:47:53 CST 2026 x86_64
Last login: Fri Mar  6 23:21:46 2026 from 192.168.2.12
username@fnos:~$ python3 report-trimrpc-mcp-202603062124.py 
trimrpc MCP Server 已启动
  监听: http://0.0.0.0:9800
  端点: POST http://<IP>:9800/mcp
  工具: get_system_logs, trimrpc_call, discover_services

  MCP 配置: {"mcpServers": {"fnos": {"url": "http://<IP>:9800/mcp"}}}

启动后,一个简单的 MCP HTTP 服务就已经运行,并对外提供三个工具接口:

get_system_logs:获取系统日志
trimrpc_call:通用 RPC 调用
discover_services:发现可用 RPC 服务

给AI配置MCP

使用 Claude CLI 添加 MCP 服务:

claude mcp add --transport http fnos http://192.168.2.254:9800/mcp

执行命令后,CLI 会将 MCP 配置写入本地配置文件:

PS C:\> claude mcp add --transport http fnos http://192.168.2.254:9800/mcp
Added HTTP MCP server fnos with URL: http://192.168.2.254:9800/mcp to local config
File modified: C:\Users\admin\.claude.json [project: C:\]
PS C:\> claude mcp get fnos
fnos:
  Scope: Local config (private to you in this project)
  Status: ✗ Failed to connect
  Type: http
  URL: http://192.168.2.254:9800/mcp

To remove this server, run: claude mcp remove "fnos" -s local
Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76

调用MCP能力

我们先看看当前的日志
img.png

配置完成后,在 Claude CLI 中即可直接调用 MCP 提供的能力:
让我们直接获取一下日志信息

PS E:\workspace\fnos\demod1> claude

╭─── Claude Code v2.1.70 ───────────────────────────────────────────────────────────────────────────────────────────╮
**                                               ** Tips for getting started                                          **
**                 Welcome back!                 ** Run /init to create a CLAUDE.md file with instructions for Claude **
**                                               ** ───────────────────────────────────────────────────────────────── **
**                                               ** Recent activity                                                   **
**                    ▐▛███▜▌                    ** No recent activity                                                **
**                   ▝▜█████▛▘                   **                                                                   **
**                     ▘▘ ▝▝                     **                                                                   **
**   Opus 4.6 (1M context) · API Usage Billing   **                                                                   **
**           E:\workspace\fnos\demod1            **                                                                   **
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

  Welcome to Opus 4.6

❯ /mcp
  ⎿  MCP dialog dismissed

❯ 获取fnos的mcp全部功能,以列表形式列出

● Based on the available MCP tools, here are all fnOS MCP functions:

  **─────**──────────────────────────────**──────────────────────────────────────────────────────────────────────────────────────────────────────────────**
  **  #  **           Function           **                                                 Description                                                  **
  **─────**──────────────────────────────**──────────────────────────────────────────────────────────────────────────────────────────────────────────────**
  ** 1   ** mcp__fnos__get_system_logs   ** 获取 fnOS 系统事件日志。返回系统操作记录,包括用户登录、系统配置变更、SSH 连接等事件。支持分页和按模块筛选。 **
  **─────**──────────────────────────────**──────────────────────────────────────────────────────────────────────────────────────────────────────────────**
  ** 2   ** mcp__fnos__trimrpc_call      ** 通用 fnOS trimrpc RPC 调用。通过 RPC Broker 调用 fnOS 上的任意已注册 RPC 服务。服务名必须以 com. 开头。      **
  **─────**──────────────────────────────**──────────────────────────────────────────────────────────────────────────────────────────────────────────────**
  ** 3   ** mcp__fnos__discover_services ** 发现 fnOS 上可用的 RPC 服务。查询 RPC Broker 获取服务路由信息,包括 UDS 路径、IP、类型、认证令牌等。         **
  **─────**──────────────────────────────**──────────────────────────────────────────────────────────────────────────────────────────────────────────────**

  参数详情

  get_system_logs:
  - module (int, 默认 -1) — 模块筛选,-1 表示全部模块
  - page (int, 默认 1) — 页码,从 1 开始
  - pageSize (int, 默认 20) — 每页条数,范围 1-100

  trimrpc_call:
  - service (string, 必填) — 目标服务名,如 com.trim.storage
  - method (string, 必填) — 方法名,如 get_info
  - params (object, 默认 {}) — 调用参数

  discover_services:
  - services (string[], 默认 []) — 要查询的服务名列表,如 ['com.trim.storage']

  核心能力是通过 discover_services 发现可用服务,再通过 trimrpc_call 调用具体服务方法,以及通过 get_system_logs 查看系统日志。

❯  获取最新的12条日志

● fnos - get_system_logs (MCP)(page: 1, pageSize: 12)
  ⎿  {
       "data": {
         "data": {
     … +73 lines (ctrl+o to expand)

● 最新 12 条系统日志(共 3067 条):

  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  **  #  **        时间         **   用户   ** 模块 **               事件内容               **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 1   ** 2026-03-04 18:44:03 ** username ** 登录 ** 登录成功(via token) IP:::1           **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 2   ** 2026-03-04 18:02:23 ** username ** 登录 ** 登录成功(via SSH) IP:192.168.2.12    **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 3   ** 2026-03-04 18:01:53 ** username ** 登录 ** 登录成功(via SSH) IP:192.168.2.12    **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 4   ** 2026-03-04 18:01:45 ** username ** 登录 ** 登录成功(via SSH) IP:192.168.2.12    **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 5   ** 2026-03-04 16:36:52 ** username ** 登录 ** 登录成功(via token) IP:::1           **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 6   ** 2026-03-04 04:59:03 ** username ** 登录 ** 登录成功(via token) IP:192.168.2.12  **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 7   ** 2026-03-03 20:10:25 ** username ** 登录 ** 登录成功 IP:::1                      **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 8   ** 2026-03-03 16:58:12 ** username ** 登录 ** 登录成功(via token) IP:::1           **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 9   ** 2026-03-03 16:32:14 ** username ** 登录 ** 登录成功 IP:::1                      **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 10  ** 2026-03-03 09:36:20 ** system   ** 系统 ** 应用 影视 启用成功                   **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 11  ** 2026-03-03 09:36:14 ** system   ** 系统 ** 应用 影视 更新成功                   **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**
  ** 12  ** 2026-03-03 09:34:12 ** username ** 登录 ** 登录成功(via token) IP:192.168.2.217 **
  **─────**─────────────────────**──────────**──────**──────────────────────────────────────**

❯ discover_services

● fnos - discover_services (MCP)
  ⎿  {
       "data": {
         "errno": 100000004,
     … +6 lines (ctrl+o to expand)

● 空列表查询失败,让我尝试查询一些常见的 fnOS 服务。

● fnos - discover_services (MCP)(services: ["com.trim.storage","com.trim.main","com.trim.eventlogger","com.trim.docker","com.trim.network","com.trim.user","com.trim.system
                                ","com.trim.app","com.trim.filemanager","com.trim.share","com.trim.rpcbroker","com.trim.notify","com.trim.backup","com.trim.disk","com.trim
                                .power","com.trim.update","com.trim.samba","com.trim.nfs","com.trim.webdav","com.trim.ddns"])
  ⎿  [
       {
         "id": "com.trim.main",
     … +31 lines (ctrl+o to expand)

● 发现了部分服务,让我再探索更多。

● fnos - discover_services (MCP)(services: ["com.trim.storage.manager","com.trim.usb","com.trim.raid","com.trim.smart","com.trim.ftp","com.trim.rsync","com.trim.vpn","com.
                                trim.firewall","com.trim.dns","com.trim.certificate","com.trim.task","com.trim.cron","com.trim.log","com.trim.monitor","com.trim.**","co
                                m.trim.plugin","com.trim.appstore","com.trim.vm","com.trim.photo","com.trim.music"])
  ⎿  []

● fnos - discover_services (MCP)(services: ["com.trim.filemanager.ui","com.trim.file","com.trim.smb","com.trim.afp","com.trim.iscsi","com.trim.power.manager","com.trim.acc
                                ount","com.trim.auth","com.trim.gateway","com.trim.proxy","com.trim.scheduler","com.trim.media","com.trim.download","com.trim.sync","com.tr
                                im.cloud","com.trim.notification","com.trim.hardware","com.trim.firmware","com.trim.config","com.trim.service"])
  ⎿  []

● 以下是在 fnOS 上发现的可用 RPC 服务:

  **─────**──────────────────────**─────────────────────────**───────────────────────────────**──────**
  **  #  **       服务 ID        **          名称           **           UDS 路径            ** 类型 **
  **─────**──────────────────────**─────────────────────────**───────────────────────────────**──────**
  ** 1   ** com.trim.main        ** TRIM Service            ** /run/trim_srv.socket          ** 1    **
  **─────**──────────────────────**─────────────────────────**───────────────────────────────**──────**
  ** 2   ** com.trim.eventlogger ** Logger Service          ** /run/trim_app_cgi/eventlogger ** 0    **
  **─────**──────────────────────**─────────────────────────**───────────────────────────────**──────**
  ** 3   ** com.trim.network     ** Network Manager Service ** /run/trim_app_cgi/network     ** 0    **
  **─────**──────────────────────**─────────────────────────**───────────────────────────────**──────**
  ** 4   ** com.trim.share       ** Share File Service      ** /run/trim_app_cgi/share       ** 0    **
  **─────**──────────────────────**─────────────────────────**───────────────────────────────**──────**

  共发现 4 个已注册的 RPC 服务:
  - com.trim.main — 核心主服务
  - com.trim.eventlogger — 事件日志服务(系统日志查询)
  - com.trim.network — 网络管理服务
  - com.trim.share — 文件共享服务

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ▪▪▪ ─
❯ 调用 com.trim.main 的方法看看有哪些功能
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ? for shortcuts

结束语

MCP 的出现,使大模型具备了调用外部服务的能力。
从某种意义上来说,这让模型不再只是“回答问题”,而是开始具备与真实系统交互的能力。

不过,这也带来了新的问题。

如果某一天 AI 误操作,或者逻辑出现偏差,通过 MCP 调用接口删除了系统中的关键数据,该如何处理?

在无法很好解决这种风险之前,大模型与现实系统之间的深度交互,仍然需要谨慎对待。

不过在考虑AI之前,不知道本地应用如果直接调用trimrpc会有应用中心上架的合规风险吗?

username@fnos:~$ ls -la /run/trim_app_cgi/rpcbroker
srwxrw-rw- 1 root root 0 Feb 27 10:37 /run/trim_app_cgi/rpcbroker
这些都是废话,不建议浪费时间看

trimrpc

0. 术语缩写

缩写 含义
SSO Small String Optimization, libstdc++ 在 sizeof(std::string) = 32 时内联 15 字节以下字符串
RB-tree std::_Rb_tree, GCC libstdc++ 红黑树实现, 用于 std::map
DCL Double-Checked Locking, 双重检查锁定单例模式
UDS Unix Domain Socket
vtable C++ 虚函数表, 通过 [rdi] 间接调用
PLT/GOT Procedure Linkage Table / Global Offset Table, ELF 延迟绑定机制

1. 体系结构概述

trimrpc 是 fnOS 自研的本地 IPC 框架。客户端通过 RpcClient API 向目标服务发起 RPC 调用, 底层通过 UDS 传输二进制编码的数据包。框架支持双协议栈: RpcPacket (30 字节固定头, 面向 AppCgi 型服务, ServiceType=0) 和 TrimSrv (12 字节头, 面向 TrimSrv 型服务, ServiceType=1)。

所有服务通过中央 Broker (/run/trim_app_cgi/rpcbroker) 进行注册与发现。客户端先向 Broker 申请权限获取目标服务的 UDS 路径和服务类型, 随后直连目标服务进行 RPC 通信 — 这是服务发现模式, Broker 不参与后续数据转发。

调用层                                    传输层
 **───────────────────────**            **──────────────────────────────**
 **  RpcClient  (0x259b0) **            **  RpcConnections  (singleton) **
 **    ** Call()   0x25bf0  ** ─Caller──▶**    ** Init()    0x196b0       **
 **    ** Apply() 0x27740  **  0x15480   **    ** Connect() 0x1a920       **
 **    ** Verify  0x256c0  **            **    ** Send()    0x192d0       **
 **───────────────────────**            **    ** OnRecv()  0x17490       **
                                      **───────────**────────────────**
                                                   ** ne* 网络引擎
                                                   ▼
                                        Unix Domain Socket
                                      /var/run/rpc/<service>

2. RpcPacket 二进制协议解析

2.1 RpcPacket::RpcPacket() — 构造函数 @ 0x1DDF0

此函数初始化一个 0xA8 字节的 RpcPacket 对象, 写入 magic 常量和协议版本, 并设置四个 SSO 字符串的内联指针。

; trimrpc::RpcPacket::RpcPacket(void)
; 对象大小 = 0xA8 (168 字节)
; rdi = this 指针

_ZN7trimrpc9RpcPacketC2Ev:
  1ddf0  mov  rax, cs:_ZTVN7trimrpc9RpcPacketE_ptr  ; 加载 RpcPacket vtable 地址
  1ddf7  mov  dword ptr [rdi+8], 54525043h           ; [this+0x08] = magic = 0x54525043
                                                      ;   即 ASCII "CPRT" (little-endian)
  1ddfe  mov  qword ptr [rdi+10h], 0                 ; [this+0x10] = session_id = 0
  1de06  add  rax, 10h                               ; 跳过 RTTI 偏移, 指向第一个虚函数
  1de0a  mov  qword ptr [rdi+18h], 0                 ; [this+0x18] = data_len/token_len = 0
  1de12  mov  [rdi], rax                             ; [this+0x00] = vtable 指针

  ; version 字段 (2 字节)
  1de15  mov  eax, 1
  1de1a  mov  [rdi+0Ch], ax                          ; [this+0x0C] = version = 1

  ; 初始化四个 SSO 字符串的内联缓冲区指针
  ; 每个 std::string 占 32 字节: [ptr(8) + size(8) + inline_buf(16)]
  1de1e  lea  rax, [rdi+38h]                         ; data 字符串的内联缓冲区
  1de22  mov  [rdi+28h], rax                         ; [this+0x28].ptr = &this->inline_buf_data
  1de26  lea  rax, [rdi+58h]                         ; token 字符串
  1de2a  mov  [rdi+48h], rax                         ; [this+0x48].ptr = &this->inline_buf_token
  1de2e  lea  rax, [rdi+78h]                         ; extra_data 字符串
  1de32  mov  [rdi+68h], rax                         ; [this+0x68].ptr = &this->inline_buf_extra
  1de36  lea  rax, [rdi+98h]                         ; extra_data2 字符串
  1de3d  mov  [rdi+88h], rax                         ; [this+0x88].ptr = &this->inline_buf_extra2

  ; reserved 字段
  1de44  mov  eax, cs:dword_315F4                    ; 加载全局默认 reserved 值
  1de4a  mov  dword ptr [rdi+24h], 0                 ; [this+0x24] = extra_data2_len = 0

  ; 将四个字符串置空 (size=0, inline_buf[0]='\0')
  1de51  mov  qword ptr [rdi+30h], 0                 ; data.size = 0
  1de59  mov  byte ptr [rdi+38h], 0                  ; data.inline_buf[0] = '\0'
  1de5d  mov  qword ptr [rdi+50h], 0                 ; token.size = 0
  1de65  mov  byte ptr [rdi+58h], 0                  ; token.inline_buf[0] = '\0'
  1de69  mov  qword ptr [rdi+70h], 0                 ; extra_data.size = 0
  1de71  mov  byte ptr [rdi+78h], 0                  ; extra_data.inline_buf[0] = '\0'
  1de75  mov  qword ptr [rdi+90h], 0                 ; extra_data2.size = 0
  1de80  mov  byte ptr [rdi+98h], 0                  ; extra_data2.inline_buf[0] = '\0'
  1de87  mov  [rdi+20h], eax                         ; [this+0x20] = reserved (从全局)
  1de8a  retn

对象内存布局 (0xA8 字节):

偏移      大小  字段
───────── ───── ──────────────────────────────
0x00      8     vtable*
0x08      4     magic           = 0x54525043 ("CPRT")
0x0C      2     version         = 1
0x0E      2     (padding)
0x10      8     session_id      (8 字节会话标识)
0x18      2     data_len
0x1A      2     token_len
0x1C      4     extra_data_len
0x20      4     reserved
0x24      4     extra_data2_len
0x28      32    std::string data       (JSON 请求/响应)
0x48      32    std::string token      (UUID / 认证令牌)
0x68      32    std::string extra_data
0x88      32    std::string extra_data2

2.2 RpcPacket::DeserializeHeader() @ 0x1DEA0

解析 30 字节的网络字节流到 RpcPacket 对象的头部字段。

; trimrpc::RpcPacket::DeserializeHeader(const char* buf, uint len)
; rdi = this, rsi = buf 指向 30 字节原始头部

_ZN7trimrpc9RpcPacket17DeserializeHeaderEPKcj:
  1dea0  mov  eax, [rsi]             ; eax = *(uint32_t*)(buf+0) = magic
  1dea2  mov  [rdi+8], eax           ; this->magic = magic

  1dea5  movzx eax, word ptr [rsi+4] ; eax = *(uint16_t*)(buf+4) = version
  1dea9  mov  [rdi+0Ch], ax          ; this->version = version

  1dead  mov  rax, [rsi+6]           ; rax = *(uint64_t*)(buf+6) = session_id
  1deb1  mov  [rdi+10h], rax         ; this->session_id = session_id

  1deb5  movzx eax, word ptr [rsi+0Eh] ; eax = *(uint16_t*)(buf+14) = data_len
  1deb9  mov  [rdi+18h], ax            ; this->data_len = data_len

  1debd  movzx eax, word ptr [rsi+10h] ; eax = *(uint16_t*)(buf+16) = token_len
  1dec1  mov  [rdi+1Ah], ax            ; this->token_len = token_len

  1dec5  mov  eax, [rsi+12h]          ; eax = *(uint32_t*)(buf+18) = extra_data_len
  1dec8  mov  [rdi+1Ch], eax          ; this->extra_data_len = extra_data_len

  1decb  mov  eax, [rsi+16h]          ; eax = *(uint32_t*)(buf+22) = reserved/flags
  1dece  mov  [rdi+20h], eax          ; this->reserved = reserved

  1ded1  mov  eax, [rsi+1Ah]          ; eax = *(uint32_t*)(buf+26) = extra_data2_len
  1ded4  mov  [rdi+24h], eax          ; this->extra_data2_len = extra_data2_len

  1ded7  retn

头部线格式 (30 字节, 无对齐填充):

Byte:  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D
       **──magic──** **vers** **───────session_id────────** **dlen** **tlen** **─extra_len─** **─reserved──** **─extra2_len**

2.3 RpcPacket::Serialize() @ 0x1DC30

将 RpcPacket 对象序列化为网络传输的二进制字符串: 30 字节头 + data + token + extra_data + extra_data2。

; trimrpc::RpcPacket::Serialize(void) -> std::string
; rdi = 返回值 (std::string*), rsi = this

_ZN7trimrpc9RpcPacket9SerializeB5cxx11Ev:
  1dc30  push r12
  1dc32  push rbp
  1dc33  mov  rbp, rdi                ; rbp = 返回 string 对象指针
  1dc36  push rbx
  1dc37  mov  rbx, rsi                ; rbx = this (RpcPacket*)

  ; 将各数据段长度写入头部字段
  1dc3a  mov  rax, [rsi+30h]          ; rax = data.size()
  1dc3e  mov  rcx, [rsi+50h]          ; rcx = token.size()
  1dc42  mov  rdi, [rbx]              ; rdi = vtable*
  1dc45  mov  [rsi+18h], ax           ; this->data_len = (uint16_t)data.size()
  1dc49  mov  rdx, [rbx+70h]          ; rdx = extra_data.size()
  1dc4d  mov  [rsi+1Ah], cx           ; this->token_len = (uint16_t)token.size()
  1dc51  mov  r8, [rdi+28h]           ; r8 = vtable->data_length (虚函数指针)
  1dc55  lea  rdi, data_length_func   ; rdi = &RpcPacket::data_length 静态地址
  1dc5c  mov  rsi, [rsi+90h]          ; rsi = extra_data2.size()
  1dc63  mov  [rbx+1Ch], edx          ; this->extra_data_len = extra_data.size()
  1dc66  mov  [rbx+24h], esi          ; this->extra_data2_len = extra_data2.size()

  ; 检查是否是基类的 data_length 实现 (非虚覆盖)
  1dc69  cmp  r8, rdi
  1dc6c  jnz  loc_1DDD0              ; 若被覆盖, 走虚调用路径

  ; 计算总长度 = 0x1E + data_len + token_len + extra_data_len + extra_data2_len
  1dc72  movzx eax, ax               ; data_len
  1dc75  movzx ecx, cx               ; token_len
  1dc78  add  edx, esi               ; extra_data_len + extra_data2_len
  1dc7a  add  eax, ecx               ; data_len + token_len
  1dc7c  add  eax, edx               ; + extra sizes
  1dc7e  lea  rdi, [rbp+10h]         ; rdi = 返回 string 的内联缓冲区
  1dc82  lea  r12d, [rax+1Eh]        ; r12 = total_len = payload + 30 字节头
  1dc86  mov  [rbp+0], rdi           ; string.ptr = inline buf (SSO 尝试)
  1dc8a  cmp  r12, 0Fh
  1dc8e  ja   loc_1DDA0              ; 超过 15 字节, 堆分配

  ; 分配完毕后填充 30 字节头部
  1dcaa  mov  eax, [rbx+8]           ; magic
  1dcad  add  rdi, 1Eh               ; rdi 指向头部之后 = 数据区起始
  1dcb1  mov  [rdi-1Eh], eax         ; 写入 magic @ offset 0
  1dcb4  movzx eax, word ptr [rbx+0Ch]
  1dcb8  mov  [rdi-1Ah], ax          ; 写入 version @ offset 4
  1dcbc  mov  rax, [rbx+10h]
  1dcc0  mov  [rdi-18h], rax         ; 写入 session_id @ offset 6
  1dcc4  movzx eax, word ptr [rbx+18h]
  1dcc8  mov  rdx, [rbx+30h]         ; rdx = data.size()
  1dccc  mov  [rdi-10h], ax          ; 写入 data_len @ offset 14
  1dcd0  movzx eax, word ptr [rbx+1Ah]
  1dcd4  mov  [rdi-0Eh], ax          ; 写入 token_len @ offset 16
  1dcd8  mov  eax, [rbx+1Ch]
  1dcdb  mov  [rdi-0Ch], eax         ; 写入 extra_data_len @ offset 18
  1dcde  mov  eax, [rbx+20h]
  1dce1  mov  [rdi-8], eax           ; 写入 reserved @ offset 22
  1dce4  mov  eax, [rbx+24h]
  1dce7  mov  [rdi-4], ax            ; 写入 extra_data2_len @ offset 26
                                      ; (注: 仅写 2 字节, 高 2 字节来自对齐)

  ; 依次 memcpy 四个数据段
  1dceb  test rdx, rdx               ; data.size() == 0?
  1dcee  jnz  loc_1DD80              ; 有数据则 memcpy data.c_str()

  1dcf4  mov  rdx, [rbx+50h]         ; token.size()
  1dcf8  test rdx, rdx
  1dcfb  jnz  loc_1DD60              ; memcpy token

  1dcfd  mov  rdx, [rbx+70h]         ; extra_data.size()
  1dd01  test rdx, rdx
  1dd04  jnz  loc_1DD38              ; memcpy extra_data

  1dd06  mov  rdx, [rbx+90h]         ; extra_data2.size()
  1dd0d  test rdx, rdx
  1dd10  jnz  loc_1DD20              ; memcpy extra_data2

  1dd12  mov  rax, rbp               ; 全部为空, 直接返回
  1dd15  pop  rbx
  1dd16  pop  rbp
  1dd17  pop  r12
  1dd19  retn

2.4 TrimSrvResponsePacket::DeserializeHeader() @ 0x21320

TrimSrv 协议的 12 字节头部解析。协议设计极简 — 无魔数校验, 无版本字段。

; trimrpc::TrimSrvResponsePacket::DeserializeHeader(const char* buf, uint len)
; rdi = this, rsi = buf (12 字节), edx = len

_ZN7trimrpc21TrimSrvResponsePacket17DeserializeHeaderEPKcj:
  21320  push r14
  21322  push r13
  21324  push r12
  21326  mov  r12d, edx              ; r12d = len (保存用于后续 memcpy)
  21329  push rbp
  2132a  mov  rbp, rsi               ; rbp = buf
  2132d  push rbx
  2132e  mov  rbx, rdi               ; rbx = this

  ; 解析 12 字节头部:
  ;   [0..3]  uint32 packet_size — 整包大小(含头部 12 字节)
  ;   [4..11] uint64 session_id
  21335  mov  rax, [rsi+4]           ; rax = *(uint64_t*)(buf+4) = session_id
  21339  mov  r13d, [rsi]            ; r13d = *(uint32_t*)(buf+0) = packet_size
  21341  mov  [rdi+28h], rax         ; this->session_id = session_id (offset 0x28)

  ; 将 packet_size 转换为字符串存入 this->data
  ; (后续通过 data_length() 虚函数返回 packet_size - 12)
  ; ... SSO 字符串赋值逻辑 ...
  21345  lea  r14, [rsp+48h+src+8]
  21349  cmp  r13, 0Fh
  2134d  ja   loc_21408             ; 大于 SSO 阈值则堆分配

  ; ... 字符串赋值与 memcpy 完成 ...

  ; 最终将原始 buf 数据 memcpy 到 this->data 区
  213ca  add  rsp, 20h
  213ce  mov  edx, r12d              ; n = len
  213d1  mov  rsi, rbp               ; src = buf
  213d4  pop  rbx
  213dc  jmp  _memcpy               ; 尾调用 memcpy

2.5 TrimSrvRequestPacket::Serialize() @ 0x163E0

TrimSrv 请求包的序列化极其简单 — 直接返回内部 data 字符串的拷贝, 无额外包装。

; trimrpc::TrimSrvRequestPacket::Serialize(void) -> std::string
; rdi = 返回 string*, rsi = this

_ZN7trimrpc20TrimSrvRequestPacket9SerializeB5cxx11Ev:
  163e0  push r12
  163e2  push rbp
  163e3  push rbx
  163e4  mov  rbp, [rsi+10h]         ; rbp = this->data.size() (offset 0x10)
  163e8  mov  rbx, rdi               ; rbx = 返回值指针
  163eb  add  rdi, 10h
  163ef  mov  [rbx], rdi              ; return_string.ptr = &inline_buf
  163f2  mov  r12, [rsi+8]           ; r12 = this->data.c_str() (offset 0x08)
  163f6  cmp  rbp, 0Fh
  163fa  ja   loc_16448              ; 大于 15 字节 → 堆分配

  163fc  cmp  rbp, 1
  16400  jnz  loc_16420              ; != 1 → 可能为 0 或需 memcpy

  ; 单字节快速路径
  16402  movzx eax, byte ptr [r12]   ; 读取唯一字节
  16407  mov  [rbx+10h], al          ; 写入内联缓冲区
  1640a  mov  [rbx+8], rbp           ; return_string.size = 1
  1640e  mov  rax, rbx
  16411  mov  byte ptr [rdi+rbp], 0  ; 追加 '\0'
  16415  pop  rbx
  16416  pop  rbp
  16417  pop  r12
  16419  retn

  ; 多字节路径
  16420  test rbp, rbp               ; size == 0?
  16423  jz   loc_1640A              ; 是, 直接设 size=0
  16425  mov  rdx, rbp               ; n = size
  16428  mov  rsi, r12               ; src = data.c_str()
  1642b  call _memcpy               ; memcpy(inline_buf, src, n)
  16430  mov  rdi, [rbx]
  16433  mov  [rbx+8], rbp           ; return_string.size = n
  16437  mov  rax, rbx
  1643a  mov  byte ptr [rdi+rbp], 0  ; 追加 '\0'
  1643e  pop  rbx
  1643f  pop  rbp
  16440  pop  r12
  16442  retn

  ; 堆分配路径 (size > 15)
  16448  test rbp, rbp
  1644b  js   loc_1647D              ; 负值 → basic_string::_M_create 异常
  1644d  mov  rdi, rbp
  16450  add  rdi, 1                 ; 分配 size+1 字节
  16456  call __Znwm                 ; operator new(size+1)
  1645b  mov  [rbx+10h], rbp         ; capacity = size
  1645f  mov  rdx, rbp
  16462  mov  rsi, r12
  16465  mov  [rbx], rax             ; ptr = heap_buf
  16468  mov  rdi, rax
  1646b  call _memcpy
  16470  mov  rdi, [rbx]
  16473  jmp  loc_16433              ; 设 size 并追加 '\0'

3. RpcClient — 公共 API 层

3.1 RpcClient::RpcClient() 构造函数 @ 0x259B0

; trimrpc::RpcClient::RpcClient(const string& uuid, const string& service_name)
; rdi = this (0x88 字节), rsi = &uuid, rdx = &service_name

_ZN7trimrpc9RpcClientC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_:
  259b0  push r15
  259b2  push r14
  259b4  lea  r14, [rdi+18h]         ; r14 → this->uuid 的内联缓冲区
  259b8  push r13
  259ba  mov  r13, rdx               ; r13 = &service_name
  259bd  push r12
  259bf  mov  r12, rsi               ; r12 = &uuid
  259c2  push rbp
  259c3  push rbx
  259c4  mov  rbx, rdi               ; rbx = this
  259c7  sub  rsp, 8

  ; 初始化 caller 指针为 NULL
  259cb  mov  rbp, [rsi+8]           ; rbp = uuid.size()
  259cf  mov  [rdi+8], r14           ; this->uuid.ptr = &inline_buf
  259d3  mov  qword ptr [rdi], 0     ; this->caller_ = nullptr (offset 0)
  259da  mov  r15, [rsi]             ; r15 = uuid.data()
  259dd  cmp  rbp, 0Fh
  259e1  ja   loc_25B20              ; uuid > 15 字节 → 堆分配

  ; uuid SSO 拷贝 (size == 1 快速路径)
  259e7  cmp  rbp, 1
  259eb  jnz  loc_25B10
  259f1  movzx eax, byte ptr [r15]
  259f5  mov  [rdi+18h], al          ; inline_buf[0] = uuid[0]

  259f8  mov  rax, r14
  259fb  mov  [rbx+10h], rbp         ; this->uuid.size = 1
  259ff  mov  byte ptr [rax+rbp], 0  ; '\0' 终止

  ; 拷贝 service_name 到 this+0x28
  25a03  lea  rbp, [rbx+38h]         ; service_name 内联缓冲区
  25a07  mov  [rbx+28h], rbp
  25a0b  mov  r15, [r13+0]           ; service_name.data()
  25a0f  mov  r13, [r13+8]           ; service_name.size()
  ; ... SSO 拷贝逻辑同上 ...

  ; 分配内部 Caller 对象 (0xD0 字节)
  25a3d  pxor xmm0, xmm0
  25a42  mov  edi, 0D0h              ; sizeof(Caller) = 208
  ; ... call operator new + Caller::Caller() ...
  ; 最终 this->caller_ = new Caller(uuid)

  ; 初始化其余字段
  ; [this+0x48] = error_code = 0
  ; [this+0x50] = response_string (SSO 空串)
  ; [this+0x70] = response shared_ptr = {nullptr, nullptr}
  ; [this+0x84] = pid = getpid()

RpcClient 对象布局 (0x88 = 136 字节):

偏移      大小  字段
───────── ───── ──────────────────────────
0x00      8     Caller*         指向内部 Caller 对象
0x08      32    std::string     uuid
0x28      32    std::string     service_name
0x48      4     int32_t         error_code
0x4C      4     (padding)
0x50      32    std::string     response_string
0x70      16    shared_ptr      response (Response 对象)
0x80      4     int32_t         last_call_error
0x84      4     int32_t         pid (= getpid())

3.2 RpcClient::VerifyServiceId() @ 0x256C0

验证 RPC 目标服务名的合法性。仅执行两项检查: 长度上限和固定前缀。

; trimrpc::RpcClient::VerifyServiceId(const string& service_name)
; rdi = this, rsi = &service_name
; 返回: eax = 1 (合法) 或 0 (非法, 并设置 error_code)

_ZN7trimrpc9RpcClient15VerifyServiceIdERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE:
  256c0  sub  rsp, 8

  ; —— 检查 1: 长度 ≤ 128 ——
  256c4  mov  rax, [rsi+8]           ; rax = service_name.size()
  256c8  cmp  rax, 80h               ; 与 128 比较
  256ce  ja   loc_25718              ; 超过 → 错误码 5

  ; —— 检查 2: 前缀 "com." ——
  256d0  mov  rdx, [rsi]             ; rdx = service_name.data()
  256d3  cmp  rax, 3                 ; size <= 3 → 不可能以 "com." 开头
  256d7  ja   loc_25700              ; size >= 4, 可以检查

  ; 验证失败 → 错误码 6
  256d9  mov  dword ptr [rdi+80h], 6 ; this->last_call_error = 6
  256e3  lea  rsi, aInvalidClientN  ; "Invalid client name"
  256ea  mov  edi, 3                 ; LOG_ERR
  256ef  xor  eax, eax
  256f1  call _syslog               ; syslog(LOG_ERR, "Invalid client name")
  256f6  xor  eax, eax              ; return 0 (验证失败)
  256f8  add  rsp, 8
  256fc  retn

  ; 检查前 4 字节 == "com." (0x2E6D6F63, little-endian)
  25700  cmp  dword ptr [rdx], 2E6D6F63h  ; *(uint32_t*)data == "com."
  25706  jnz  loc_256D9                    ; 不匹配 → 错误码 6
  25708  mov  eax, 1                       ; return 1 (验证通过)
  2570d  add  rsp, 8
  25711  retn

  ; 长度超限 → 错误码 5
  25718  mov  dword ptr [rdi+80h], 5       ; this->last_call_error = 5
  25722  lea  rsi, aInvalidClientI         ; "Invalid client id, exceed the maximum..."
  25729  mov  edi, 3                       ; LOG_ERR
  2572e  xor  eax, eax
  25730  call _syslog
  25735  jmp  loc_256F6                    ; return 0

要点: 0x2E6D6F63 按小端序分解为 63 6F 6D 2E = c o m .。验证逻辑仅检查前 4 字节与长度, 不做任何 ACL 或命名空间隔离。

3.3 RpcClient::Call(service, method, data) @ 0x25BF0

核心调用入口。验证服务名后委托 Caller::Call, 然后根据 ServiceType 创建对应的 Response 对象。

; trimrpc::RpcClient::Call(const string& service, const string& method, const string& data)
; rdi = this, rsi = &service, rdx = &method, rcx = &data

_ZN7trimrpc9RpcClient4CallERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_S8_:
  25bf0  push r15
  25bf2  push r14
  25bf4  push r13
  25bf6  mov  r13, rcx               ; r13 = &data
  25bf9  push r12
  25bfb  mov  r12, rsi               ; r12 = &service
  25bfe  push rbp
  25bff  mov  rbp, rdx               ; rbp = &method
  25c02  push rbx
  25c03  mov  rbx, rdi               ; rbx = this (RpcClient*)
  25c06  sub  rsp, 48h

  ; 步骤 1: 验证服务名
  25c0a  call _ZN7trimrpc9RpcClient15VerifyServiceIdE...  ; VerifyServiceId(service)
  25c0f  test al, al
  25c11  jz   loc_25E80              ; 验证失
收藏
送赞
分享

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则