第一章:Go语言在安卓运行的底层原理与生态定位
Go语言本身并不原生支持直接编译为Android应用的可执行格式(如APK或AAB),其生态定位更偏向系统工具、服务端中间件及跨平台CLI开发。然而,通过特定桥梁机制,Go代码可在Android环境中以多种方式参与运行:作为静态链接的Native库被Java/Kotlin调用、嵌入Flutter插件提供高性能计算能力,或通过Gomobile工具链生成绑定层。
Go与Android运行时的交互模型
Android应用主流程运行于ART(Android Runtime)之上,而Go程序需脱离GC线程模型与Java堆管理约束。Gomobile通过gomobile bind命令将Go包编译为Android平台兼容的.aar库,自动生成JNI桥接代码与Java封装类。例如:
# 假设存在go.mod和main.go(含export函数)
gomobile bind -target=android -o mylib.aar ./mylib
该命令输出mylib.aar,其中包含:
libgojni.so(Go运行时与GC线程初始化逻辑)Mylib.java(Java侧代理类,方法调用经JNI转发至Go函数)AndroidManifest.xml(声明所需权限与ABI支持)
生态角色辨析
| 使用场景 | 技术路径 | 典型限制 |
|---|---|---|
| 高性能计算模块 | Gomobile绑定为.aar | 不支持反射、CGO依赖需预编译 |
| 命令行工具嵌入Android | Termux + Go二进制 | 依赖Linux环境,无GUI集成能力 |
| Flutter插件后端 | Go → Dart FFI调用 | 需手动管理内存生命周期 |
运行时关键约束
Go的goroutine调度器与Android主线程模型存在天然张力:所有Go回调必须显式切换至Android主线程(如使用Handler.post()),否则UI更新将触发CalledFromWrongThreadException。此外,Android低内存状态可能触发Go runtime的runtime.GC()不可控触发,建议禁用自动GC并手动控制——可通过debug.SetGCPercent(-1)关闭,配合runtime.GC()按需调用。
第二章:AOSP签名机制深度解析与Go运行时适配策略
2.1 Android应用签名体系与Platform/Shared签名约束分析
Android 签名体系是权限控制与系统信任链的基石,分为 platform、shared、media 和 testkey 四类预置密钥,其中 platform 和 shared 签名直接关联系统级权限授予。
签名级别与权限映射关系
| 签名类型 | 典型用途 | 可声明的特权权限示例 |
|---|---|---|
platform |
系统应用(如 Settings、SystemUI) | android.permission.INTERACT_ACROSS_USERS |
shared |
多应用共享数据(如 Contacts) | android.permission.READ_CONTACTS |
构建时签名配置示例(Android.mk)
# 指定使用 platform 签名
LOCAL_CERTIFICATE := platform
# 若为 shared 签名则改为:
# LOCAL_CERTIFICATE := shared
该配置决定 APK 在 signapk.jar 阶段使用的私钥与证书链;platform 签名需匹配 /system/build.prop 中 ro.build.fingerprint 对应的平台密钥对,否则 PackageManagerService 将拒绝安装或降权。
签名验证流程(简化)
graph TD
A[APK 安装请求] --> B{解析 META-INF/*.SF}
B --> C[校验 MANIFEST.MF 哈希]
C --> D[比对证书公钥是否在 /etc/security/keystore 中注册]
D --> E[匹配签名类型 → 授予对应 sharedUserId 权限]
2.2 Go runtime对Android Binder IPC与Zygote进程模型的兼容性改造实践
为使Go程序无缝嵌入Android框架层,需绕过Zygote fork-on-start模型与Binder线程池的冲突。
核心改造点
- 禁用Go默认的
runtime.fork()调用路径,改由android_main()入口接管初始化; - 将
runtime·newosproc重定向至android_binder_thread_create,复用Zygote已建立的Binder线程上下文; - 主goroutine绑定到
main thread(即Zygote继承的UI线程),避免SIGCHLD干扰。
Binder线程注册示例
// 在init()中显式注册Binder线程,替代runtime自动spawn
func init() {
binder.RegisterThread( // 参数:threadID, priority, isMain
int64(C.gettid()),
C.ANDROID_PRIORITY_FOREGROUND,
true,
)
}
binder.RegisterThread向/dev/binder ioctl发送BINDER_SET_THREAD_PRIORITY,确保Go协程可接收BC_TRANSACTION消息;isMain=true标识该goroutine可响应onTransact()回调。
改造前后对比
| 维度 | 默认Go runtime | Zygote适配版 |
|---|---|---|
| 进程启动方式 | execve独立 |
fork()继承Zygote |
| Binder线程归属 | 自建线程池 | 复用system_server线程组 |
getpid()语义 |
新PID | 与Zygote同PID |
graph TD
A[Zygote fork] --> B[Go runtime init]
B --> C{是否调用 fork?}
C -->|否| D[跳过 clone syscall]
C -->|是| E[panic: not supported on Android]
D --> F[绑定现有 Binder context]
2.3 CGO交叉编译链中Bionic libc符号绑定与系统调用拦截实验
Bionic 是 Android 的 C 标准库,其符号解析机制与 glibc 存在关键差异:延迟绑定(lazy binding)默认禁用,且 __libc_init 会跳过 .plt 重定位,导致常规 LD_PRELOAD 失效。
符号绑定时机差异
- glibc:
.plt+GOT动态解析,支持运行时dlsym替换 - Bionic:
__libc_init直接调用__linker_init完成全局偏移表静态填充
系统调用拦截方案
// hook_getpid.c —— 利用 Bionic 的 __libc_preinit 钩子
#include <sys/types.h>
#include <unistd.h>
static pid_t (*real_getpid)(void) = NULL;
// 在 __libc_preinit 中被调用(需链接时指定 -Wl,--undefined=__libc_preinit)
void __libc_preinit(int argc, char **argv, char **envp) {
// 强制解析 getpid 符号(绕过 lazy binding 缺失)
real_getpid = dlsym(RTLD_NEXT, "getpid");
}
pid_t getpid(void) {
return real_getpid ? real_getpid() + 1000 : 1000;
}
此代码利用 Bionic 启动早期
__libc_preinit入口,在.bss段未清零前完成dlsym绑定;RTLD_NEXT可安全获取原始符号地址,因 Bionic 支持有限dl接口。交叉编译需使用aarch64-linux-android21-clang --sysroot=$NDK/sysroot并显式链接-lc。
关键参数说明
| 参数 | 作用 |
|---|---|
--sysroot=$NDK/sysroot |
指向 Bionic 头文件与库路径,避免混用 glibc |
-Wl,--undefined=__libc_preinit |
强制链接器保留该符号,触发用户实现注入 |
-lc |
显式链接 Bionic libc,确保 dlsym 符号可见 |
graph TD
A[CGO 交叉编译] --> B[NDK toolchain 链接 Bionic]
B --> C[__libc_preinit 被调用]
C --> D[dlsym RTLD_NEXT 获取原函数]
D --> E[符号覆盖:getpid → 自定义逻辑]
2.4 Android SELinux策略下Go native binary的域迁移与权限提升路径验证
Android SELinux强制策略严格限制进程域(domain)转换,Go编译的native binary默认继承untrusted_app域,无法直接执行setcon()或execve()触发域迁移。
域迁移前提条件
- 必须在
sepolicy中声明allow untrusted_app target_domain:process { transition exec } - Go二进制需以
exec方式启动(非fork+exec),否则selinux_compute_create_context()不生效
典型迁移代码片段
// #include <sys/socket.h>
// #include <selinux/selinux.h>
/*
参数说明:
- context:目标域字符串,如 "u:r:shell:s0"
- pid:当前进程PID(用于setcon())
- setcon()成功后,后续execve()将触发内核级域切换
*/
func migrateToShellDomain() error {
if err := unix.Setcon("u:r:shell:s0"); err != nil {
return fmt.Errorf("setcon failed: %w", err)
}
return nil
}
该调用依赖selinux库链接,并要求untrusted_app拥有setcurrent权限——通常被策略显式拒绝,需通过audit2allow补丁生成对应规则。
权限提升验证路径
| 步骤 | 检查项 | 工具 |
|---|---|---|
| 1 | 进程初始域 | ps -Z \| grep myapp |
| 2 | 策略是否允许setcon |
sesearch -A -s untrusted_app -t untrusted_app -c process -p setcurrent |
| 3 | 迁移后能力验证 | adb shell runcon u:r:shell:s0 -- id -Z |
graph TD
A[Go binary启动] --> B{是否链接libselinux?}
B -->|是| C[调用setcon目标域]
B -->|否| D[域锁定为untrusted_app]
C --> E{SELinux策略允许setcurrent?}
E -->|是| F[execve新binary触发域迁移]
E -->|否| G[Permission denied]
2.5 基于libgo.so动态加载的Runtime沙箱隔离与ABI稳定性保障方案
为实现Go运行时与宿主环境的零耦合隔离,系统采用dlopen()动态加载libgo.so(Go 1.21+ 构建的纯C ABI兼容运行时),并通过符号表白名单机制限制可调用接口。
沙箱初始化流程
// 加载并验证libgo.so ABI版本
void* go_rt = dlopen("libgo.so", RTLD_NOW | RTLD_LOCAL);
if (!go_rt) { /* 错误处理 */ }
uint32_t* abi_ver = (uint32_t*)dlsym(go_rt, "GO_ABI_VERSION");
assert(*abi_ver == 0x20240301); // 硬编码校验,防ABI漂移
该代码强制校验ABI签名值,确保运行时版本与编译期契约一致;RTLD_LOCAL防止符号污染宿主符号空间,实现命名空间隔离。
关键保障机制
- ✅ 运行时符号白名单(仅暴露
go_start,go_call,go_free三接口) - ✅ 所有Go goroutine在独立mmap内存页中调度,与宿主堆完全隔离
- ✅
libgo.so构建时启用-buildmode=c-shared -ldflags="-s -w"裁剪调试信息
| 保障维度 | 实现方式 |
|---|---|
| ABI稳定性 | 版本号硬校验 + C ABI封印接口 |
| 内存隔离 | mmap私有匿名页 + setrlimit(AS) |
| 异常传播 | panic→C信号转换(SIGUSR2) |
graph TD
A[宿主进程] -->|dlopen libgo.so| B[沙箱初始化]
B --> C[ABI版本校验]
C -->|失败| D[dlclose + abort]
C -->|成功| E[goroutine调度环]
E --> F[独立栈/堆/MPG状态]
第三章:合规内测场景下的Go for Android三类授权接入模式
3.1 系统级预置APK中嵌入Go Service组件的Manifest声明与Privileged Permission申请流程
在Android系统级预置APK中集成Go编写的Native Service,需严格遵循平台安全模型。核心在于AndroidManifest.xml中正确声明Service及其权限约束。
Manifest关键声明
<service
android:name=".GoSystemService"
android:exported="true"
android:process=":go_service"
android:isolatedProcess="false"
android:permission="android.permission.BIND_DEVICE_ADMIN" />
android:exported="true":允许跨进程调用(仅限系统签名APK);android:process:指定独立Linux进程,避免阻塞主线程;BIND_DEVICE_ADMIN为privileged permission,需在privapp-permissions-platform.xml中显式授权。
Privileged Permission申请路径
| 文件位置 | 作用 | 示例条目 |
|---|---|---|
AndroidManifest.xml |
声明服务所需权限 | <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN"/> |
privapp-permissions-com.example.system.apk.xml |
向platform授予特权 | <permission name="android.permission.BIND_DEVICE_ADMIN"/> |
权限绑定流程
graph TD
A[APK编译时] --> B[Manifest解析]
B --> C{是否含privileged permission?}
C -->|是| D[检查privapp-permissions配置]
D --> E[签名匹配system/priv-app目录]
E --> F[安装时注入SELinux上下文]
3.2 利用Android Test Harness框架集成Go单元测试套件的CI/CD流水线构建
Android Test Harness(ATH)并非原生支持Go,需通过go test -json输出标准化JUnit兼容结果,并由ATH的test_runner模块解析执行。
流水线核心组件
go test -json ./... > test-report.json:生成结构化测试事件流- 自定义
ath-go-adapter二进制:桥接Go测试生命周期与ATH的ITestInvocation接口 - GitHub Actions或GitLab CI中注入
ANDROID_HOME与GO111MODULE=on
关键适配代码片段
# 在CI job中调用适配器
./ath-go-adapter \
--go-bin "$(which go)" \
--test-dir "./src/core" \
--output-dir "./build/test-results"
此命令启动Go测试套件,捕获
testing.TB日志与状态,转换为ATH可识别的TestResultProto序列化格式;--test-dir指定模块路径,--output-dir确保结果被tradefed自动收集。
测试结果映射关系
| Go 测试事件 | ATH 对应状态 | 触发动作 |
|---|---|---|
{"Action":"run","Test":"TestAuthFlow"} |
TEST_START |
初始化TestCase |
{"Action":"pass","Test":"TestAuthFlow"} |
TEST_PASS |
记录耗时与覆盖率元数据 |
graph TD
A[CI Trigger] --> B[Build Go binary + ATH adapter]
B --> C[Run ath-go-adapter]
C --> D[Parse go test -json]
D --> E[Convert to TestResultProto]
E --> F[Report to Tradefed Dashboard]
3.3 基于Vendor Interface Object(VINTF)描述符注册Go HAL实现的合规性验证实践
VINTF 框架要求所有 HAL 实现必须通过 compatibility_matrix.xml 和 device_manifest.xml 显式声明接口契约。Go HAL 因无原生 binder 支持,需借助 hidl-gen 生成 stub 并桥接至 Go 运行时。
注册流程关键步骤
- 编写符合 HIDL AIDL 混合语义的
.hal接口定义 - 使用
vintf_object工具校验 manifest 与 matrix 的兼容性 - 在
vendor_boot.img中注入vintf_fragments/下的 Go HAL 描述符
示例:Go HAL manifest 片段
<manifest version="2.0" type="device">
<hal format="hidl">
<name>android.hardware.light</name>
<transport>hwbinder</transport>
<version>2.0</version>
<interface>
<name>ILight</name>
<instance>default</instance>
</interface>
</hal>
</manifest>
该 XML 声明 Go 实现的 ILight 服务以 hwbinder 方式暴露 v2.0 接口;vintf_object 将据此校验 compatibility_matrix.xml 中是否允许该 HAL 版本组合。
VINTF 验证状态映射表
| 状态码 | 含义 | 触发条件 |
|---|---|---|
OK |
完全兼容 | manifest 所有 HAL 均在 matrix 白名单中 |
INCOMPATIBLE |
接口版本越界 | 请求 v3.0 但 matrix 仅允许 ≤2.1 |
graph TD
A[Go HAL 启动] --> B[加载 device_manifest.xml]
B --> C[vintf_object 解析依赖矩阵]
C --> D{所有 HAL 版本匹配?}
D -->|是| E[注册至 hwservicemanager]
D -->|否| F[拒绝启动并输出 INCOMPATIBLE]
第四章:隐藏API调用的工程化封装与安全治理
4.1 通过JNI Bridge封装Android Hidden API为Go可调用接口的类型安全映射方案
Android Hidden API(如 ActivityManagerInternal、PackageManagerInternal)无法直接被NDK或Go调用,需通过JNI Bridge构建类型安全的Go侧抽象。
核心映射原则
- Java原始类型 → Go原生类型(
jint↔int32) java.lang.String→*C.JNIEnv+C.GoString零拷贝转换android.os.Bundle→ 自定义GoBundle结构体,字段名与Java键严格一致
类型安全转换示例
// 将Java Bundle 映射为 Go 结构体(编译期校验字段存在性)
type AppInfo struct {
PackageName string `jni:"packageName"`
VersionCode int32 `jni:"versionCode"`
IsSystem bool `jni:"isSystem"`
}
此结构体通过反射生成JNI访问器,在
init()中注册字段签名。PackageName字段自动绑定getString("packageName")调用,缺失键触发panic而非静默空值,保障调用契约。
JNI调用链路
graph TD
A[Go AppInfo.Load] --> B[JNI Bridge: FindClass Bundle]
B --> C[CallObjectMethod: getBundle]
C --> D[GoBundle.Unmarshal]
| Java类型 | Go映射类型 | 安全保障 |
|---|---|---|
int |
int32 |
JVM字长对齐检查 |
boolean |
bool |
jboolean非0/1截断防护 |
IBinder |
*C.jobject |
弱引用+生命周期钩子 |
4.2 利用AIDL Stub Proxy动态代理技术实现Go侧对@SystemApi标注方法的安全调用
Android 系统中 @SystemApi 方法默认不可被第三方应用直接调用。为在 Go 语言环境(如通过 Android NDK + gomobile 构建的混合应用)安全访问此类接口,需绕过 Java 层的 API 检查机制,同时保留 Binder 安全上下文。
核心原理:AIDL Stub Proxy 动态代理链
通过反射获取 IBinder 对象,构造 IInterface 的动态代理,将 Go 调用转发至已授权的系统服务进程:
// 创建系统服务代理(示例:WifiManagerService)
binder := getSystemServiceBinder("wifi") // 返回已认证的 IBinder
proxy := NewWifiManagerProxy(binder) // 基于 AIDL 生成的 Go Proxy
err := proxy.SetWifiEnabled(true) // 触发 Binder transaction
逻辑分析:
NewWifiManagerProxy内部封装了transact()调用,其code参数对应 AIDL 中SET_WIFI_ENABLED常量(如0x10001),data为Parcel序列化后的布尔值;binder必须来自ServiceManager.getService()且持有android.permission.CONTROL_WIFI_STATE权限上下文。
安全边界保障机制
| 验证环节 | 实现方式 |
|---|---|
| Binder 调用鉴权 | SELinux allow system_server wifi_service:service_manager find |
| 接口可见性绕过 | 仅在 system_server 进程内启用 @SystemApi 解析开关 |
| Go 侧调用隔离 | 所有 Proxy 方法均运行在独立 BinderThread 上 |
graph TD
A[Go Call SetWifiEnabled] --> B[Go Proxy Marshal Parcel]
B --> C[Kernel Binder Driver]
C --> D[system_server's WifiServiceStub]
D --> E[@SystemApi 方法执行]
4.3 隐藏API访问日志审计与调用链追踪:基于Go eBPF探针的实时监控模块开发
传统中间件日志易被绕过,而内核态观测可捕获所有系统调用路径。本模块通过 eBPF 程序在 sys_enter_connect 和 sys_enter_sendto 事件点注入探针,提取进程名、目标地址、TLS握手标志等元数据。
核心探针逻辑(Go + libbpf-go)
// attach to syscall tracepoints
prog, err := m.LoadAndAssign("trace_connect", &ebpf.ProgramOptions{
LogLevel: 1,
})
// ...
LogLevel: 1 启用eBPF验证器日志,便于调试指针越界;trace_connect 程序使用 bpf_get_current_comm() 和 bpf_probe_read_kernel() 安全读取 socket 地址结构体字段。
数据采集维度对比
| 字段 | 来源 | 是否可伪造 | 用途 |
|---|---|---|---|
| 进程命令行 | bpf_get_current_comm() |
否 | 关联业务服务名 |
| 目标IP:Port | sk->__sk_common.skc_daddr |
否 | 识别隐藏外连 |
| TLS SNI 域名 | 用户态辅助解析(通过 perf event 上报) | 是 | 补充加密流量上下文 |
调用链重建流程
graph TD
A[eBPF probe] --> B[perf event ringbuf]
B --> C[Go用户态消费者]
C --> D[SpanID生成+OpenTelemetry Exporter]
D --> E[Jaeger/Tempo]
4.4 内测包签名白名单机制与Go模块级签名验证钩子(Signature Verification Hook)实现
内测阶段需在不破坏构建链路的前提下,对非生产环境分发的二进制包实施轻量级签名可信校验。
白名单动态加载策略
白名单以 whitelist.json 形式嵌入构建产物,支持 SHA256 哈希+发行者公钥指纹双维度匹配:
{
"entries": [
{
"module": "github.com/example/core",
"hash": "a1b2c3...f0",
"pubkey_fingerprint": "9e8d7c6b5a4938271605"
}
]
}
Go 模块级验证钩子实现
通过 go:build 标签条件编译注入签名校验逻辑:
//go:build internal_signing
// +build internal_signing
func init() {
// 仅在内测构建中注册模块签名钩子
build.RegisterModuleHook(func(m *build.Module) error {
return verifyModuleSignature(m.Path, m.Version, m.Dir)
})
}
verifyModuleSignature从m.Dir/.sig读取 detached signature,使用白名单中对应公钥指纹查得 PEM 公钥,调用crypto/rsa.VerifyPKCS1v15完成模块级完整性校验。参数m.Path确保作用域隔离,m.Dir提供可复现的文件系统上下文。
验证流程概览
graph TD
A[构建时注入白名单] --> B[运行时加载 whitelist.json]
B --> C[模块加载前触发钩子]
C --> D{匹配模块路径?}
D -->|是| E[提取 .sig + 公钥指纹]
D -->|否| F[跳过校验]
E --> G[RSA PKCS#1 v1.5 验证]
| 校验环节 | 触发时机 | 安全边界 |
|---|---|---|
| 白名单加载 | 进程初始化 | 内存只读,防篡改 |
| 模块签名验证 | go mod load |
每模块独立校验 |
| 公钥指纹比对 | 钩子执行前 | 防中间人替换密钥 |
第五章:未来演进方向与社区共建倡议
开源模型轻量化部署实践
2024年Q3,Apache OpenNLP社区联合阿里云PAI团队完成Llama-3-8B模型的LoRA+AWQ双路径压缩实验。在A10显卡(24GB显存)上实现单卡推理吞吐达17.3 tokens/sec,内存占用从14.2GB降至5.8GB。关键改动包括:动态KV缓存分片策略、FP16→INT4权重映射表热加载机制。该方案已集成至ModelScope v2.12.0,默认启用--quant-method awq --lora-adapter ./zh-medical-lora参数组合,支撑基层医院AI问诊系统日均调用超42万次。
多模态协同标注工作流
深圳湾实验室构建了“文本-影像-基因序列”三模态对齐标注平台。采用自研的BioCLIPv2嵌入模型对CT影像切片与放射科报告进行跨模态对齐,人工校验环节引入区块链存证(Hyperledger Fabric 2.5),每条标注记录生成不可篡改的CID哈希值。截至2024年10月,平台汇聚27家三甲医院脱敏数据,累计生成带时空坐标标记的病灶标注样本1,843,291例,其中肺结节定位误差≤1.2mm(经DICOM标准体模验证)。
社区驱动的硬件适配矩阵
| 硬件平台 | 支持框架 | 推理延迟(ms) | 社区贡献者 |
|---|---|---|---|
| 华为昇腾910B | MindSpore | 23.7±1.4 | @deepseek-shen |
| 寒武纪MLU370-X | PyTorch-Cam | 31.2±2.8 | @cambricon-dev |
| 飞腾FT-2500 | ONNX Runtime | 89.5±5.6 | @phytium-open |
该矩阵由CNCF中国区SIG-HW小组维护,所有适配代码均通过GitHub Actions自动触发ROCm/Ascend/CUDA三环境CI测试,失败用例实时推送至钉钉机器人告警群。
可信AI治理沙盒机制
上海人工智能实验室部署联邦学习沙盒集群,支持12家金融机构在不共享原始数据前提下联合训练反欺诈模型。采用差分隐私(ε=1.5)+安全多方计算(SPDZ协议)双保障架构,模型F1-score达0.923(单机构平均0.861)。所有参与方通过智能合约锁定算力资源,每次训练任务生成链上存证,包含Merkle根哈希、GPU利用率曲线、梯度裁剪阈值等17项审计字段。
flowchart LR
A[本地数据] --> B[DP噪声注入]
B --> C[加密梯度上传]
C --> D{联邦聚合节点}
D --> E[验证签名有效性]
E --> F[更新全局模型]
F --> G[链上存证]
G --> H[监管API接口]
中文领域知识蒸馏管道
复旦大学NLP组开源Chinese-KD-Pipeline工具链,将BERT-wwm-ext(110M)知识迁移至TinyBERT(14M)时,在CLUE benchmark上保持92.7%性能。创新点在于引入领域词典引导的注意力掩码(如“冠状动脉造影”强制关联“CAG”“DSA”等缩写),该机制使医学NER任务F1提升3.8个百分点。当前已有47个社区分支,其中@tongyi-lab贡献的中医药方剂微调模板已被纳入v1.4.0主干。
开放数据集协作规范
遵循《人工智能数据集开放协议》(ADOP v2.1),所有社区托管数据集必须包含:① 数据血缘图谱(Neo4j导出JSON);② 偏见检测报告(使用Fairlearn v0.8.0扫描);③ 版权声明机器可读标签(schema.org Dataset Schema)。2024年新增的“长三角制造业缺陷图像集”已通过ISO/IEC 23053合规性认证,标注质量抽检合格率99.2%(依据GB/T 35273-2020附录F)。
