
把 Android 模拟器里的 /system 分区改成可写是我做系统级调试时绕不开的一步。无论是往系统目录里塞测试文件、改 build.prop 调屏幕密度还是想在模拟器里卸载一个顽固的内置应用都会先撞上 Read-only file system 这堵墙。这堵墙的钥匙就是 remount。我在不同模拟器上折腾 remount 折腾了挺长时间踩过不少坑也搞清楚了一些容易被忽略的原理这篇文章把整个过程整理出来包括它到底做了什么、不同模拟器有什么差异、报错怎么排查希望能帮准备做系统级调试或自动化测试的朋友省点时间。1. remount 底层在做什么从挂载点到只读系统分区1.1 先理解“挂载”这个概念Android 里一堆目录其实不是真实存在的“文件夹”而是各种存储区域挂载出来的访问入口。/system、/vendor、/data、/cache 这些路径背后都是独立分区。说人话分区是房间里的柜子挂载点是柜子的门。你打开 /system 这个门看到的是 system 分区柜子里的东西。mount 命令做的事情就是把柜子和门绑定起来同时决定这扇门能不能往外搬东西。默认情况下 Android 出厂时系统分区的挂载状态是只读ro你只能从里面读文件不能往里写。这不是什么玄学而是 Android 故意设计成这样的系统分区里的文件是系统正常运行的基础意外修改可能直接导致开不了机所以默认锁死。1.2 remount 的本质mount -o remount,rwremount 的意思是“重新挂载”对应命令本质是mount -o remount,rw /system它不会把系统分区卸载再挂载那样中间会有短暂不可访问而是直接在现有挂载基础上把权限标志从只读改成读写。这时系统分区的挂载点就是 rw 了往里写文件就变成了可能。adb remount 命令做的事更“傻瓜化”它自动判断当前设备里的系统级分区比如 /system、/vendor批量执行 remount 操作省得你一个个 mount。这里有一个小细节有些精简版模拟器里的 adbd 阉割过 adb remount 这条命令那就可以用上边那句原始 mount 命令代替后面我细说。1.3 dm-verity 和 userdebug为什么模拟器能 remount真机反而难只说 mount 命令还不够因为 Android 4.4 以后引入了 dm-verity 机制。它在块设备层面做哈希校验每个数据块被改动过校验就会失败系统会拒绝读取等于你“改是改了但系统不认”。所以就算你把分区 remount 成 rw只要 dm-verity 还开着修改也无法稳定生效。好在模拟器平台的镜像基本都是 userdebug 或 eng 版本。这类构建版本默认把 dm-verity 关掉或允许临时关闭而且 adb 拥有 root 权限。所以我一条 adb root adb remount 就能搞定真机尤其量产 user 版连 adb root 都用不了这就是模拟器做系统级调试舒服的关键。可以简单对比一下环境adb root 支持dm-verityremount 难度量产真机user 版不支持开启基本无法完成Android 模拟器 AVDuserdebug支持关闭/可控一条命令第三方模拟器雷电/MUMU 开 Root 后多数支持无/关闭需要先开 Root 开关1.4 Android 10 之后的动态分区看着复杂操作思路没变Android 10 开始系统分区不再是独立的物理分区而是几个逻辑分区合在 super 分区里动态划分/system、/product、/vendor 都是 super 里的动态分区。很多人一看“动态分区”就头大总觉得 remount 是不是失效了。实测下来在 userdebug 版本的模拟器上 adb remount 依然能正常工作命令内部已经处理了动态分区的 remount 细节不需要我们手动指定分区块设备。需要注意的是如果你拿一台 Android 11 以上真机userdebug 版做同样操作偶尔会提示需要先 adb disable-verity 再重启那属于另一个链路模拟器上我还没遇到过必须这么做的情况。知道有这么回事遇到奇怪的提示时能判断方向就够了。这一部分核心意思是remount 不是绕开系统安全机制的“漏洞”而是 Android 给调试构建留的后门。理解这一点后面无论换什么模拟器、什么版本你都能推算出它能不能 remount。2. 动手前先确认三件事版本、Root 权限、ADB 环境2.1 确认模拟器系统版本我一开始犯过一个错误拿 Android 9 的思路去处理 Android 12 的模拟器试了半天以为命令错了其实是版本差异导致入口不同。建议先跑一句adb shell getprop ro.build.version.release同时看下 build typeadb shell getprop ro.build.typero.build.type 是 user 还是 userdebug直接决定了 remount 这条路走不走得通。我看到很多人在第三方模拟器上 remount 失败一查 ro.build.type 是 user那基本没戏必须先在模拟器设置里打开自带的 Root 权限开关改完这个属性值才会变成可调试状态。2.2 检查是否真的拥有 root 权限连接模拟器后先执行adb root这个命令的输出有很多种输出adbd is already running as root说明已经是 root直接往下走。输出restarting adbd as root说明刚才还不是 root现在 adbd 正在以 root 身份重启等几秒重新连上即可。输出adbd cannot run as root in production builds这是最常见的失败提示说明当前系统构建是 production/user 版本不允许 adb root。这时应该回到模拟器设置里找“Root 权限”开关而不是硬扛命令。想要更直观地验证可以adb shell id看到uid0(root)就是 root。另外可以看两个属性adb shell getprop ro.debuggable adb shell getprop ro.securero.debuggable 为 1说明系统允许调试ro.secure 为 0 通常意味着 adbd 不缺权限。这两个值在不同模拟器上表现不一致组合起来看能帮你快速定位问题。2.3 不同模拟器的 Root 开关位置这一点我放到第 4 章细说但动手前你必须先清楚不是所有模拟器开箱就能 adb root。Android Studio 自带 AVD 默认是 userdebug开箱可以雷电、MUMU 这类游戏模拟器出厂是精简过的 Android通常需要在图形设置界面打开 Root 选项否则 adb root 必报 production builds 错误。Genymotion 则是虚拟机镜像很多版本默认自带 root 权限连开关都不用。还有个容易被忽略的点优先用模拟器自带的 adb。第三方模拟器为了兼容游戏经常魔改系统组件自带的 ADB 工具版本匹配度最高。如果你全局用的 platform-tools 版本和模拟器的 adb 版本跨度太大握手阶段会出现各种玄学问题。遇到“offline”“unauthorized”反复无常时先换模拟器目录里的 adb 试试。3. 标准操作流程从 adb devices 到验证可写3.1 确认设备连接状态先跑 adb devices把模拟器加入设备列表。官方 AVD 一般显示为 emulator-5554 或 emulator-5556第三方模拟器则是自定义端口连接后的设备号。如果你同时开了多个模拟器实例注意区分目标设备用-s 设备号指定避免命令发错设备。adb devices -l如果设备列表里看不到你的模拟器要先手动连接。雷电、MUMU 这类模拟器的 IP 端口不是标准 5555需要在模拟器设置里的“ADB 调试”相关页面查看端口号然后adb connect 127.0.0.1:7555很多教程爱写死一个端口号但不同版本差异很大凭经验猜端口等于浪费时间。去模拟器设置页面看一眼5 秒钟的事。3.2 三步走adb root、adb remount、验证写入以官方 AVD 为例标准链路是adb root adb wait-for-device adb remountadb root 之后 adbd 会带着 root 权限重启连接会短暂断开所以最好等设备重新就绪再执行下一步。有些版本 adb remount 会输出Remount succeeded有些会多几行关于 overlayfs 的信息不用慌只要没报错基本成功了。如果你的模拟器对 adb remount 无响应或提示 command not found这通常意味着它的 adbd 被阉割过直接改用通用 mount 命令adb shell mount -o rw,remount /system多分区需要可写时可以重复执行adb shell mount -o rw,remount /vendor adb shell mount -o rw,remount /product3.3 验证别只看提示自己写个文件确认我见过有人 remount 明明输出成功往 /system 里写文件还是失败。所以要验证不要只看命令提示。推荐两步验证第一步看挂载状态adb shell mount | grep /system 输出里 mount options 一栏如果是 rw说明挂载点层面已经可写。第二步直接写入测试文件adb shell echo remount-test /system/remount_test.txt adb shell cat /system/remount_test.txt能读到内容说明这条链路真的通了。测试完记得删掉避免污染系统adb shell rm /system/remount_test.txt想恢复只读状态执行adb shell mount -o ro,remount /system恢复只读不是一个必须步骤但如果你接下来要做 OTA 类操作或者需要系统校验完整性建议改完顺手恢复别留个半开的后门在那。4. 各家模拟器实测记录AVD、雷电、MUMU 的差异与坑4.1 Android Studio AVD-writable-system 是最大隐藏坑官方 AVD 在 remount 里算最友好的adb root 默认可用命令链路最短。但它的坑也很隐蔽如果你直接在 Android Studio 里点击运行按钮启动模拟器系统分区是以临时层方式挂载的remount 改的内容在你关掉模拟器之后就没了下次启动一切还原。正确做法是用命令行带参数启动emulator -avd 你的AVD名 -writable-system加上 -writable-system 后模拟器允许你对系统分区写入并且改动在本次运行期间有效。需要持久化时还要配合 -no-snapshot 避免加载旧快照把改动覆盖掉。这个参数很多教程不提导致一堆人改完 hosts、build.prop一重启就发现啥也没变误以为自己操作错了。另外 AVD 的镜像类型也值得注意Google APIs 镜像相比 AOSP 镜像阉割少调试体验更顺畅如果你想改的东西涉及系统设置直接用 Google APIs 镜像能少踩很多坑。4.2 雷电模拟器先开 Root 开关再考虑 adbd 差异雷电模拟器的 remount 链路最典型的问题是很多人没在设置里开 Root。默认状态下 adb root 会直接报 production builds 错误因为它的系统本身就按 user 逻辑跑。你在“设置-其他设置”里找到 Root 权限打开之后模拟器会重启这时 adb root 大概率就能用了。雷电还有个特点是 adbd 被魔改过adb remount 这条命令有没有被保留取决于具体版本。我的实测经验是先用 adb shell id 看权限如果已经是 uid0直接用 mount 命令更省事adb shell mount -o rw,remount /system雷电连接端口也不固定一般可以在安装目录下看到 adb.exe或者用它的命令行工具 ldconsole.exe 辅助操作。连不上 5555 端口时别死磕去设置里看端口。4.3 MUMU 模拟器和其他模拟器的通用思路MUMU 模拟器的 root 开关在设置里叫“Root 权限”默认是关的打开后重启模拟器即可。MUMU 的 adb 连接端口常见是 7555但也存在版本差异。一个通用思路是第三方模拟器无论如何换操作链条永远是“打开 Root 开关 - 连接 adb - adb root或直接 shell- mount -o rw,remount - 验证”。我把常见模拟器的几个关键点整理成一张表方便对照模拟器是否需要手动开 Rootadb root 是否可用remount 常见方式Android Studio AVD不需要userdebug可用adb remount雷电模拟器需要开 Root 后可用adb remount 或 mount 命令MUMU 模拟器需要开 Root 后可用主要是 mount 命令Genymotion视镜像而定多数可用adb remount 或 mount 命令还有一个通用检查remount 之后如果写入时仍然报 Operation not permitted先别急着骂模拟器大概率是 SELinux 在拦截这个我们下章细说。5. 高频报错排查六个常见坑的定位链路5.1 adbd cannot run as root in production builds这个报错最直接含义是当前 Android 构建不允许 adbd 以 root 身份运行。我遇到这个报错时第一反应不是去考证命令而是去模拟器设置里翻 Root 开关。第三方模拟器默认关掉 root 很大程度是为了规避游戏厂商的检测但它确实给调试造成了障碍。打开开关重启后这条报错基本消失。如果模拟器根本没有 Root 开关某些精简版确实没有那就只能换镜像或换模拟器。别浪费时间改 ro.debuggable 之类的属性很多第三方镜像会在启动时强制覆写这些值改了也白改。5.2 remount 成功但写入报 Operation not permitted这是最容易让人困惑的组合remount 输出的提示明明成功mount 一看也是 rw但 adb push 或 echo 写入时还是报 Operation not permitted。问题大概率出在 SELinux 上。Android 的 SELinux 策略默认 enforcing它会拦截应用和进程对系统目录的写操作即使挂载标志是 rw安全策略这关过不去照样不许写。临时解法是adb shell setenforce 0执行后 SELinux 变成 permissive 模式只记录违规不强制拦截写入就能通过。要注意这个操作是临时的设备重启后恢复 enforcing。如果你想持久化需要修改 selinux 策略文件并重新编译镜像那已经超出 remount 的范畴了。还有一类 Operation not permitted 是文件系统层面属性的比如某个文件本身就带 immutable 属性。处理方式是查看属性adb shell lsattr /system/xxx如果有 immutable 标志用 chattr -i 去掉后再写。这种场景常见于厂商做过加固的系统镜像在模拟器上遇到概率不高但值得知道。5.3 改完重启改动全丢了这个坑我已经提过AVD 没加 -writable-system或者第三方模拟器的系统分区是只读镜像加载到内存的临时层。区别在于 AVD 加参数就能解决第三方模拟器很多压根不支持持久化系统分区即使 remount 成功重启后改动也会被镜像文件还原。解决办法看需求如果只是临时测试remount 一次用完拉到如果希望每次启动模拟器都自带某个系统修改可以考虑写脚本在每次启动后自动执行 remount 修改把“持久化”变成“启动时重复执行”这是我在自动化测试里最常用的妥协方案。5.4 快照导致的“改了像没改”模拟器支持快照功能很多人改完系统文件后习惯性保存快照但下次启动时加载的快照里系统状态和系统镜像文件状态不一致出现各种莫名问题。我给 AVD 改系统文件时会先用 -no-snapshot 启动改完也不要保存快照宁可多花点启动时间避免快照把这些临时改动固定到一个容易出问题的状态。5.5 外置存储路径的 Operation not permitted 和 remount 无关经常有人遇到这样一条报错/storage/emulated/0/Android/data/... 下无法执行 chmod报 Operation not permitted。这个和 system 分区 remount 完全是两回事。SD 卡和模拟外置存储走的是 FUSE 和分区存储权限模型应用只能访问自己目录和公共目录Android 11 以后这种限制更严格。即使你拥有 root 权限直接 chmod 这个路径下的文件也可能被 FUSE 层拒绝。这个场景我一般用下面两种方式绕用 adb push 先把文件推到 /data/local/tmp再用 root 权限的 shell 用 cp 复制到目标目录。或者检查目标应用有没有开放相关目录的访问权限。遇到这类报错先判断修改目标在哪个文件系统、由什么机制管辖别把锅全甩给 remount。5.6 USB 调试授权与端口冲突第三方模拟器经常出现 adb devices 能看到设备但状态是 offline 或 unauthorized 的情况。offline 多半是 adb 版本不匹配换模拟器自带 adb 即可unauthorized 是模拟器弹窗授权没点去模拟器屏幕上确认 RSA 授权弹窗。多开模拟器时还容易遇到端口冲突我习惯把每个模拟器的实例端口记下来并在 adb connect 时指定具体端口避免命令发到错误的实例上。6. remount 之后的典型玩法与注意事项6.1 三个高频场景改 build.prop、改 hosts、卸系统应用remount 成功之后能干的事非常多我列几个最常见的改 build.prop 调整屏幕密度sed 替换 ro.sf.lcd_density 的值比如把 480 改成 420重启后系统 UI 和布局都会按新密度渲染。这在机型适配测试里很常用。adb shell sed -i s/ro.sf.lcd_density480/ro.sf.lcd_density420/ /system/build.prop改 hosts 屏蔽广告域名用追加方式写一行到 /system/etc/hosts应用解析域名时就会走本地映射。adb shell echo 127.0.0.1 ad.example.com /system/etc/hosts卸载系统级应用remount 后可以直接在 shell 里对 /system/app 下的 apk 动手但更稳妥的方式是用 pm 命令adb shell pm list packages | grep 包名关键字 adb shell pm uninstall -k --user 0 包名这种方式不需要删文件只是针对当前用户停用系统底层文件还在恢复容易。6.2 改完系统文件后务必注意的事改 build.prop 或 hosts 之前先备份原文件。别嫌麻烦改坏了恢复有多痛苦备份就有多值得adb shell cp /system/build.prop /system/build.prop.bak另外注意别在模拟器上乱删系统组件。游戏模拟器的系统本来就被厂商精简过一个看似没用的系统应用可能承担着开机启动的依赖。删之前先查依赖关系或者干脆用 pm uninstall 的方式停用而不是物理删除。如果改坏了导致模拟器无法启动官方 AVD 可以用 -wipe-data 重置数据但系统镜像层面的损坏需要重新创建 AVD 或重装镜像。第三方模拟器的恢复手段就更少了所以我的原则是只改必要的文件只做可逆的操作一切修改都记录在案。6.3 自动化场景下的命令串如果你和我一样经常在脚本里做系统级测试建议把整个链路串成一条命令避免脚本中断adb root adb wait-for-device adb remount adb shell setenforce 0 adb shell echo test /system/auto_test.txt这样做的好处是其他同事接手时不需要理解每一步的上下文跑脚本就能复现系统状态。同时建议团队统一 platform-tools 版本否则不同人的 adb 行为不一致排查问题时非常浪费时间。最后分享一个我在自动化测试里常用的稳妥流程每次启动模拟器后先确认 ro.build.type 是 userdebug 或已开启 root 开关再执行 remount验证阶段一定用“实际写入读取”来确认而不是迷信命令输出做完修改后把改动记录到测试文档里方便模拟器重置后按步骤恢复状态。remount 本身不复杂复杂的是模拟器厂商各自的差异和隐藏的坑。把原理、操作链路和排查思路理顺之后你会发现它不过就是“确认权限 - 改挂载标志 - 验证写入”三个环节。希望这篇文章能帮你把折腾的时间省下来把精力放到真正需要调试的业务问题上。