|
|
前言
最近 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能力
我们先看看当前的日志

配置完成后,在 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
|