reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
122
vendor-new/rev-skills/.claude/skills/re-zig/SKILL.md
Normal file
122
vendor-new/rev-skills/.claude/skills/re-zig/SKILL.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
name: re-zig
|
||||
type: atomic
|
||||
description: >
|
||||
Zig 编译产物逆向:产物识别、comptime 展开、panic/错误处理路径、C ABI 边界。
|
||||
触发词:Zig逆向、zig、comptime、zig 产物、panic。
|
||||
capabilities: [lang-runtime-analysis]
|
||||
---
|
||||
|
||||
# Zig 逆向
|
||||
|
||||
## 何时使用 / 何时不用
|
||||
|
||||
- 用:Zig 产物(无 C++ RTTI/异常表、panic 函数链特征、`_start → main` 启动形态),需要还原错误处理路径、C ABI 边界、comptime 展开后的行为
|
||||
- 用:Zig/C 混合产物中区分 Zig 侧代码(无 RTTI 侧 + Zig 符号模式)
|
||||
- 不用:C/C++ 产物(走 [[re-cpp-abi]];有 RTTI/异常表即非 Zig 单方产物)
|
||||
- 不用:只需函数逻辑(直接反编译技能)
|
||||
|
||||
## 工具准备
|
||||
|
||||
### readelf / llvm-nm(符号与节分析)
|
||||
|
||||
- 安装与验证见 [[re-cpp-abi]] 工具准备
|
||||
- 用途: `readelf -S` 查异常表节;`readelf -s`/`llvm-nm` 查符号与可见性(Zig 业务函数多为 LOCAL 符号)
|
||||
|
||||
### llvm-objdump / objdump(反汇编)
|
||||
|
||||
- 安装与验证见 [[re-cpp-abi]] 工具准备
|
||||
- 用途: 定位 panic 调用点、catch/orelse 的错误码比较(`cmpw` + 分支)
|
||||
|
||||
### Ghidra / IDA(反编译底座)
|
||||
|
||||
- 安装与验证见 [[re-ghidra]] / [[re-ida]]
|
||||
- Zig 产物无类型信息(无 DWARF 时),配合行为分析(见步骤 3/4)
|
||||
|
||||
### strings(字符串池/错误名)
|
||||
|
||||
- 系统自带;验证: `strings --version`
|
||||
- 用途: `@errorName` 错误名字符串、panic 消息、格式串定位
|
||||
|
||||
### zig 编译器(可选,对照编译)
|
||||
|
||||
- 官方 tarball / `brew install zig` / Windows 官方安装器;验证: `zig version`
|
||||
- 用途: 同版本编译对照产物,验证 panic 链/错误联合布局(版本差异大,见 [[layout]])
|
||||
|
||||
## 操作步骤
|
||||
|
||||
按顺序执行,每步产物存档(路径 + sha256,见 [[re-triage]])。
|
||||
|
||||
1. **产物识别**:
|
||||
```sh
|
||||
readelf -s sample | grep -iE 'panicking|panicExtra|defaultPanic|zig' | head
|
||||
readelf -S sample | grep gcc_except_table # 应无输出(Zig 产物常带 .eh_frame,不能作判别)
|
||||
readelf -s sample | grep __gxx_personality_v0 # 应无匹配
|
||||
readelf -s sample | grep -wE '_start|main' # 启动形态
|
||||
```
|
||||
- Zig 特征:panic 函数链(`debug.panicExtra`/`debug.panicking` 等,版本相关)、`std` 符号模式(`std.debug.print` 等)、**无 C++ RTTI/异常表**(对比 [[re-cpp-abi]] 的 RTTI/异常密集特征)
|
||||
- 判别组合:无 `.gcc_except_table` + 无 `__gxx_personality_v0` + 无 `_ZTV*`(RTTI vtable)→ 无 C++ 异常机制;`.eh_frame` 两者都有,不能单独作判据
|
||||
- 与 C 混合编译:Zig 符号与 C 符号共存(见步骤 5 边界)
|
||||
|
||||
2. **符号可见性与启动路径**:
|
||||
```sh
|
||||
readelf -s sample | grep -E 'GLOBAL|LOCAL' | grep -cE 'FUNC'
|
||||
readelf -s sample | grep -wE '_start|main' | head
|
||||
```
|
||||
- 启动路径:`_start`(GLOBAL)→ 运行时初始化(std.start)→ `main`(LOCAL);Zig 的 `main` 是普通函数,入口经 std.start 包裹(exit 处理在包裹层)
|
||||
- **符号可见性**:Zig 默认只导出 `_start` 与显式 `export` 的函数,业务函数是 LOCAL 符号(debug/ReleaseSafe 符号表仍在,ReleaseFast 可 strip)——`nm` 看得到不等于导出,hook/注入面按导出表算
|
||||
- comptime 展开产物:编译期计算已内联/展开——无对应源码结构,按行为分析(见坑 1)
|
||||
|
||||
3. **panic/错误处理路径**:
|
||||
```sh
|
||||
readelf -s sample | grep -iE 'panic' | head # panic 链符号(版本相关命名)
|
||||
```
|
||||
- panic 链:`@panic`/断言失败 → panic 函数(打印 + abort)——定位 panic 调用点可找输入校验/不变量;panic 处理函数本身是"打印+退出",调用点才是业务校验
|
||||
- 错误联合(error union):`!T` 类型,布局按载荷大小分两种(小载荷 8 字节槽、错误码在高位;大载荷错误码在前、载荷按对齐内联——[[layout]] 有实测表);调用点检查 `orelse`/`catch` 分支(编译为错误码比较 + 分支)
|
||||
- 分析:错误路径是逆向重点(校验逻辑、失败分支)——错误码比较点即分支条件,错误名可经 `@errorName` 字符串池还原
|
||||
- 错误名还原:`@errorName(e)` 的字符串在 `__zig_tag_name_*` 符号/字符串池——`strings` 里错误名与代码路径直接对应,是错误语义的第一手线索
|
||||
|
||||
4. **常量与字符串定位(行为分析入口)**:
|
||||
```sh
|
||||
strings -n 5 sample | head -30 # 格式串/错误名/panic 消息
|
||||
readelf -S sample | grep -E 'rodata|data' # 常量区
|
||||
```
|
||||
- `std.debug.print` 的格式串在只读数据区,交叉引用可回到调用点(错误输出路径)
|
||||
- comptime 求值的常量直接内联为立即数,无常量表——找"魔数"按调用点回溯参数
|
||||
|
||||
5. **comptime 与泛型展开**:
|
||||
- comptime 计算的常量/内联函数无运行时痕迹;泛型实例化产生重复代码(按调用点参数特化)
|
||||
- 还原策略:按行为分析(常量出现处 → 回溯到哪个调用参数),不按源码映射
|
||||
- `std.debug.print` 等 std 函数大量内联(Release 模式),`readelf -s` 可能只剩启动与 panic 链
|
||||
|
||||
6. **C ABI 边界**:
|
||||
- `@extern` / `@cImport`:Zig 调用 C 库(导入表清晰可查——`readelf -d` 的 NEEDED 与导入符号)
|
||||
- `export fn`:Zig 侧导出给 C/宿主调用(GLOBAL 符号,导出表可见)
|
||||
- 混合产物:按符号来源区分(Zig 符号 vs C 符号——链接器分组/节归属),边界处是逻辑入口(Zig 主体逻辑在边界内侧)
|
||||
- 调用约定:**只在 `extern` / `export` 或显式 `callconv(.c)` 的边界上才是 C ABI**——编译器自身的定义是「`c` 是本目标 C 调用约定的别名;**标记为 `extern` 或 `export` 的函数默认获得该约定**」,而**普通 Zig `fn` 用的是 Zig 默认约定**(定义原文:既非 `export` 也非 `inline` 时使用;**不对栈对齐、寄存器等作任何保证,且只能在同一 Zig 编译单元内使用**)。所以 `callconv(.c)` 是**显式要求** C 约定,不是普通函数的默认值
|
||||
- **纯 Zig 内部函数不能无条件套 C ABI**:寄存器参数、聚合类型传参、返回值规则都可能与目标平台 C ABI 不同,恢复原型时以调用点附近的实际用法推断;x86-64 上的 SysV 只适用于 `.c` 在该 target 的映射,不代表 Zig 内部约定本身
|
||||
- C 库调用点的参数布局按 ABI 读(与 [[re-cpp-abi]] 的 C++ thiscall 不同,无隐藏参数/虚表间接层)——**这一条只适用于已确认的 C ABI 边界**
|
||||
|
||||
7. **stripped/ReleaseFast 兜底**:
|
||||
```sh
|
||||
strings -n 6 sample | grep -iE 'panic|error' | head # panic 消息/错误名(@errorName 字符串池)
|
||||
```
|
||||
- ReleaseFast 下符号表与 panic 链都可能被裁;按行为特征(错误码比较模式、字符串池)恢复,初勘兜底见 [[re-triage]]
|
||||
|
||||
## 跨域联合
|
||||
|
||||
- [[re-binary-core]] 网关:本技能归属(选择树「Zig 产物」分支)
|
||||
- [[re-cpp-abi]]:边界区分(无 RTTI/异常 → 非 C++)
|
||||
- [[re-imports]]:C 库边界(NEEDED/导入符号)与导出表
|
||||
- [[re-analyze/analysis-contract]]:符号表按数据契约传递
|
||||
- [[re-triage]]:初勘兜底
|
||||
|
||||
## 常见坑与陷阱
|
||||
|
||||
- **comptime 展开导致符号膨胀**:现象——产物符号与源码不对应;原因——编译期展开/内联/泛型特化;对策——按行为分析而非源码映射
|
||||
- **panic 路径误导**:现象——大量 panic 处理代码被当主逻辑;原因——错误路径与正常路径交织;对策——先分离 panic 调用点(校验),再分析正常路径
|
||||
- **错误联合布局版本差异**:现象——错误码读取错位;原因——error union 布局随版本变化(小载荷 8 字节槽/错误码高位,大载荷按对齐内联);对策——按目标版本确认布局([[layout]] 实测表),错误码恒为 u16
|
||||
- **与 C 混合编译难分界**:现象——Zig/C 符号混杂;原因——混合编译;对策——按符号来源与节归属分组,边界处进 Zig 逻辑
|
||||
- **无异常表 ≠ 无保护**:现象——误判无错误处理;原因——Zig 错误处理走 error union 不走异常表;对策——查错误联合调用点(`catch`/`orelse` 分支的错误码比较)
|
||||
- **业务函数是 LOCAL 符号**:现象——`nm` 列表里函数一大堆,但 `nm -g`(全局)只有 `_start`;原因——Zig 默认不导出业务符号;对策——hook/注入按导出表算;分析按 LOCAL 符号仍可定位
|
||||
- **panic 链命名随版本变**:现象——按 `std.debug.panic` 找符号找不到;原因——0.14+ 重构为 `debug.panicExtra`/`defaultPanic` 链(老版本 `std.debug.panic` 直接命名);对策——按 panic 调用点(`@panic` 编译产物:打印+abort 序列)定位,不依赖具体符号名
|
||||
@@ -0,0 +1,120 @@
|
||||
# Zig 最小解析示例
|
||||
|
||||
示例以本机 zig 0.17.0-dev 编译的真实产物为标本(Debug/ReleaseSafe 两种构建),readelf/反汇编输出与实测数据可逐字段对照。布局规则见 [[layout]]。
|
||||
|
||||
## 0. 标本构建
|
||||
|
||||
```zig
|
||||
// hello.zig
|
||||
const std = @import("std");
|
||||
fn check(x: u32) !u32 {
|
||||
if (x == 0) return error.BadInput;
|
||||
return x * 2;
|
||||
}
|
||||
pub fn main() void {
|
||||
const r = check(21) catch |e| {
|
||||
std.debug.print("err {s}\n", .{@errorName(e)});
|
||||
return;
|
||||
};
|
||||
std.debug.print("val {d}\n", .{r});
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
zig build-exe hello.zig -O Debug -femit-bin=hello_dbg
|
||||
zig build-exe hello.zig -O ReleaseSafe -femit-bin=hello
|
||||
```
|
||||
|
||||
## 1. 产物判别(readelf 实测)
|
||||
|
||||
```
|
||||
$ readelf -S hello | grep -E 'gcc_except|eh_frame'
|
||||
[ 2] .eh_frame_hdr
|
||||
[ 3] .eh_frame ← 有 .eh_frame 但无 .gcc_except_table(不能单独作判据)
|
||||
|
||||
$ readelf -s hello | grep -E '__gxx_personality_v0|_ZTV' # 无输出 = 无 C++ 异常/RTTI
|
||||
$ readelf -s hello | grep -E 'panic' | head -3
|
||||
26: 0000000000000000 8 TLS LOCAL DEFAULT 5 debug.panic_stage
|
||||
27: 0000000001088000 1 OBJECT LOCAL DEFAULT 9 debug.panicking
|
||||
64: 000000000107ac00 718 FUNC LOCAL DEFAULT 4 debug.panicExtra[...]
|
||||
```
|
||||
|
||||
对照要点:panic 链符号(0.17 命名 `debug.panicExtra`/`debug.panicking`,老版本是 `std.debug.panic`);`.eh_frame` 是 C 编译器统一产物,判别看 `.gcc_except_table` 与 `__gxx_personality_v0`。
|
||||
|
||||
## 2. 符号可见性(LOCAL vs GLOBAL)
|
||||
|
||||
```
|
||||
$ readelf -s hello | grep -wE '_start|main'
|
||||
2: 0000000001024c30 16 FUNC LOCAL DEFAULT 4 start._start
|
||||
838: 0000000001024c30 16 FUNC GLOBAL DEFAULT 4 _start ← 唯一默认导出
|
||||
18: 0000000001075540 388 FUNC LOCAL DEFAULT 4 hello.main ← 业务函数 LOCAL
|
||||
|
||||
$ readelf -s hello | grep FUNC | awk '$7=="GLOBAL"' | wc -l # 全局函数计数(约 1-2)
|
||||
```
|
||||
|
||||
对照要点:业务函数是 LOCAL 符号(Debug/ReleaseSafe 保留符号表但非导出);hook/注入面按 GLOBAL 算。
|
||||
|
||||
## 3. 错误联合布局(反汇编实测,0.17)
|
||||
|
||||
`check` 函数(Debug 构建)关键片段:
|
||||
|
||||
```
|
||||
00000000011df000 <hello.check>:
|
||||
11df008: 89 44 24 18 movl %eax, 0x18(%rsp) ; 参数 x
|
||||
11df00c: 83 f8 00 cmpl $0x0, %eax
|
||||
11df00f: 0f 85 12 00 00 00 jne ... ; x!=0 走正常路径
|
||||
11df015: e8 16 a3 e4 ff callq lang.returnError ; 错误返回路径(0.17 命名)
|
||||
11df01a: 48 8b 04 25 94 6a 27 01 movq 0x1276a94, %rax ; 装入错误表值(错误码在高位)
|
||||
11df02c: bb 02 00 00 00 movl $0x2, %ebx ; 正常路径: x*2
|
||||
11df05f: 48 89 54 24 10 movq %rdx, 0x10(%rsp)
|
||||
11df064: 89 44 24 10 movl %eax, 0x10(%rsp) ; 载荷(u32)在槽位低 4 字节
|
||||
11df068: 66 c7 44 24 14 00 00 movw $0x0, 0x14(%rsp) ; 错误码 u16=0(无错)在 offset+4
|
||||
|
||||
; main 中调用点(catch 检查):
|
||||
11df0e0: b8 15 00 00 00 movl $0x15, %eax ; x=21
|
||||
11df0e5: e8 16 ff ff ff callq hello.check
|
||||
11df0ea: 48 89 44 24 28 movq %rax, 0x28(%rsp) ; 8 字节槽位
|
||||
11df0ef: 66 83 7c 24 2c 00 cmpw $0x0, 0x2c(%rsp) ; ← 错误码比较(catch/orelse)
|
||||
11df0f5: 0f 85 0e 00 00 00 jne 错误分支
|
||||
```
|
||||
|
||||
对照要点:`E!u32` 是 8 字节槽位——载荷在低 4 字节、错误码 u16 在 offset +4(无错=0);`cmpw $0, slot+4` + 分支即 `catch` 编译产物。
|
||||
|
||||
## 4. 错误联合尺寸实测(sizeOf)
|
||||
|
||||
```
|
||||
$ zig build-exe sizez.zig -O ReleaseSafe -femit-bin=sizez && ./sizez
|
||||
u32: 8 u128: 32 struct16: 24 u8: 4 bool: 4
|
||||
```
|
||||
|
||||
| 载荷 | E!T 尺寸 | 说明 |
|
||||
|---|---|---|
|
||||
| u8/bool | 4 | 载荷 + u16 错误码 + 填充 |
|
||||
| u32/u64 | 8 | 错误码在载荷之后(高位侧) |
|
||||
| u128 | 32 | 错误码在前 2 字节、载荷按 16 对齐内联 |
|
||||
| struct{u64,u64} | 24 | 载荷按 8 对齐内联 |
|
||||
|
||||
## 5. 启动链与栈检查(ReleaseSafe 实测)
|
||||
|
||||
```
|
||||
$ readelf -s hello | grep -E '_start|probe'
|
||||
838: 0000000001024c30 16 FUNC GLOBAL DEFAULT 4 _start
|
||||
833: 000000000107e460 54 FUNC LOCAL HIDDEN 4 __zig_probe_stack
|
||||
|
||||
0000000001024c30 <_start>: ; 16 字节短桩
|
||||
1024c30: 48 8d 3d 21 e0 05 00 leaq 0x5e021(%rip), %rdi
|
||||
1024c37: e9 14 00 00 00 jmpq 0x1024c50 ; → std.start 初始化
|
||||
```
|
||||
|
||||
对照要点:`_start` 是短桩跳转;`__zig_probe_stack` 是栈探测(安全特性);ReleaseSafe 保留 `hello.main` LOCAL 符号与 panic 链。
|
||||
|
||||
## 实现教训(内化)
|
||||
|
||||
- 判别组合用三个独立特征:无 `.gcc_except_table` / 无 `__gxx_personality_v0` / 无 `_ZTV*`;`.eh_frame` 不算
|
||||
- 错误码恒为 u16、检查模式恒为"槽位高 16 位比较"——先找 `cmpw` + 分支即可定位所有 catch/orelse
|
||||
- 符号可见性决定 hook 面:LOCAL 符号只是线索,`export`/GLOBAL 才是宿主调用点
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 本机 0.17.0-dev 实测;用户产物以自身版本为准(panic 链命名与错误联合细节随版本变,见 [[layout]] 版本差异)
|
||||
- 与 [[re-cpp-abi]](C++ 判别)、[[re-analyze/analysis-contract]](符号表传递)配合使用
|
||||
@@ -0,0 +1,78 @@
|
||||
# Zig 二进制布局:启动路径 / 错误联合 / panic 链 / 符号可见性
|
||||
|
||||
Zig 产物与 C 产物的最大差异在错误处理(error union 而非异常表)与符号可见性(业务函数 LOCAL)。先确认 Zig 版本再读布局——panic 链命名与错误联合细节随版本演进。
|
||||
|
||||
## 启动路径(ELF,x86-64 实测 0.17.0-dev)
|
||||
|
||||
```
|
||||
_start (GLOBAL) ← 链接器入口(用户代码不可见)
|
||||
└─ std.start 运行时初始化(栈指针/安全栈、exit 处理)
|
||||
└─ main (LOCAL) ← 用户 main 函数(Zig 的 main 是普通函数)
|
||||
退出经 std.start 包裹层(return 后处理)
|
||||
```
|
||||
|
||||
- `_start` 是唯一默认导出的业务无关 GLOBAL 函数;`export fn` 才产生其他导出
|
||||
- 反汇编入口链:`_start` 短桩(16 字节级)→ std.start 初始化函数(大,含安全特性安装)
|
||||
|
||||
## 符号可见性(与 C 的重要差异)
|
||||
|
||||
| 构建模式 | 业务函数符号 | 说明 |
|
||||
|---|---|---|
|
||||
| Debug / ReleaseSafe | LOCAL(符号表保留) | `nm` 可见但非导出;`_start` 为 GLOBAL |
|
||||
| ReleaseFast / ReleaseSmall | 可被 strip | 按行为特征恢复 |
|
||||
| `export fn` | GLOBAL | 显式导出(C ABI 边界,宿主调用点) |
|
||||
|
||||
- 判别命令:`readelf -s sample | grep FUNC | awk '$7=="GLOBAL"'` 数全局函数
|
||||
- hook/注入面按导出表(GLOBAL + 动态符号)算,LOCAL 符号只是分析线索
|
||||
|
||||
## 错误联合(error union)布局
|
||||
|
||||
`E!T`(错误联合)编译为"错误码 + 载荷":错误码恒为 u16(0 = 无错),布局按载荷大小分两种。
|
||||
|
||||
### 载荷 ≤ 8 字节:整体一个槽位,错误码在载荷之后(高位侧)
|
||||
|
||||
| 载荷类型 | 槽位大小(实测 0.17) | 错误码位置 |
|
||||
|---|---|---|
|
||||
| u8 / bool | 4 字节 | 载荷之后(u8 在 offset 1) |
|
||||
| u32 | 8 字节 | offset +4(u16) |
|
||||
| u64 | 8 字节 | 高位 u16 |
|
||||
|
||||
### 载荷 > 8 字节:错误码在前 2 字节,载荷按自身对齐内联
|
||||
|
||||
| 载荷类型 | 槽位大小(实测 0.17) |
|
||||
|---|---|
|
||||
| u128(对齐 16) | 32 字节 |
|
||||
| struct { u64, u64 }(对齐 8) | 24 字节 |
|
||||
|
||||
- 反汇编识别:调用点后 `cmpw $0, 槽位+offset` + 分支 = `catch`/`orelse` 的错误码检查;错误返回路径 `call lang.returnError`(0.17 命名)后装入错误表地址
|
||||
- `@errorName(e)` 在字符串池留错误名(`__zig_tag_name_*` 符号/字符串),可还原错误语义
|
||||
|
||||
## panic 链
|
||||
|
||||
| 版本 | 链形态 |
|
||||
|---|---|
|
||||
| 0.14+(实测 0.17.0-dev) | `@panic`/断言 → `debug.panicExtra`/`debug.FullPanic.defaultPanic` → `debug.panicking` 状态 + 打印 + abort;溢出检查 `debug.FullPanic(...).integerOverflow` |
|
||||
| 更早版本 | `std.debug.panic`(直接命名) |
|
||||
|
||||
- 定位方式不依赖符号名:找"打印 + abort"序列(`__zig_probe_stack` 附近的栈检查调用也常见)
|
||||
- `debug.panicking` 是 TLS 状态(panic 期间再 panic 短路)
|
||||
|
||||
## C ABI 边界
|
||||
|
||||
- **C ABI 只在 `extern` / `export` 或显式 `callconv(.c)` 的边界上假定**——编译器定义:`c` 是本目标 C 调用的别名,`extern`/`export` 函数默认获得它;**普通 Zig `fn` 用 Zig 默认约定**(既非 `export` 也非 `inline` 时使用,不对寄存器/栈对齐作保证),逆向内部函数时不能套 C ABI 的参数与返回值规则
|
||||
- `.c` 在各 target 上有各自的映射(x86-64 Linux 为 SysV AMD64、Windows 为 x64 约定、AArch64 为 AAPCS64)——这是**边界上**的映射,不是 Zig 内部约定本身
|
||||
- 导入:`@extern`/`@cImport` → 动态符号表(`readelf -d` NEEDED + UND 符号)
|
||||
- 导出:`export fn` → GLOBAL(导出表)
|
||||
- 混合产物判别:Zig 侧无 RTTI/异常表符号(`_ZTV*`/`__gxx_personality_v0` 缺失),C++ 侧有
|
||||
|
||||
## 版本差异要点
|
||||
|
||||
- panic 链命名:0.14 前后差异大(`debug.panicExtra` vs `std.debug.panic`)
|
||||
- 错误联合布局:u16 错误码不变;槽位尺寸/对齐随版本微调(以实测为准,[[examples]] 有 0.17 实测)
|
||||
- `main` 符号形态稳定(LOCAL 普通函数);`_start` 稳定(GLOBAL)
|
||||
- ReleaseFast 可能去除 panic 链(`-Drelease-fast` 下安全检查关闭)
|
||||
|
||||
## 使用注意
|
||||
|
||||
- 静态分析无需沙箱;跨架构 Zig 产物(ARM/RISC-V)布局规则同(错误码 u16 不变),槽位尺寸按指针宽度
|
||||
- 与 [[re-analyze/analysis-contract]](符号表传递)、[[re-cpp-abi]](C++ 判别)配合使用
|
||||
Reference in New Issue
Block a user