使用 Clion + NDK 来编译和调试 Android 可执行二进制文件
刚好最近有需要编写安卓端的可执行二进制文件,以便在安卓端直接通过 shell 调用执行。官方推荐使用 Android Studio 进行开发,但是个人感觉再装一个 IDE 很麻烦,其次我不清楚 Android Studio 除了用来做 jnilibs ,能不能编译和调试二进制文件。(碰巧看到这里又知道情况的朋友可以留言告诉我一下,感谢!)所以我研究了一下怎么直接用 Clion 来编写并通过 adb 远程调试。作为小项目,最后效果也算满意。踩了一些坑,特此记录一下。
环境准备
本文在 Linux 下进行,但是 Widows 下同理,可作为参考。
- 首先需要下载 Android NDK ,这个不必多说: Android NDK 下载
- 需要 cmake、make、gdb 工具, Windows 下应该可通过 MinGW 解决
- Android Platform Tools ,提供了 adb ,直接下载就行: Android Platform Tools 下载
至此,环境准备完毕!
Clion 设置
Clion 的设置,首先在于新建项目的时候,选择新建 cmake 项目。然后会自动生产 main.c 和 CMakeLists.txt 和 cmake-build-debug 目录。很简单的两个文件, main.c 输出一个 hello word 。然后修改 CmakeList.txt ,怎么来修改呢?参考:CMake 文档: 使用 NDK 为安卓平台交叉编译 ,我的项目样例如下:
cmake_minimum_required(VERSION 3.22) # cmake 版本
project(android_server C) # 我的项目名叫 android_server ,自行对应调整
set(CMAKE_C_STANDARD 23) # C 版本,自行调整
set(CMAKE_SYSTEM_NAME Android) # 固定
set(CMAKE_SYSTEM_VERSION 24) # Android API level ,自行决定
set(CMAKE_ANDROID_ARCH_ABI arm64-v7a) # 目标二进制平台,自行决定
set(CMAKE_ANDROID_NDK /这里放下载解压的 Android NDK 的文件夹路径)
set(CMAKE_ANDROID_STL_TYPE gnustl_static) # 反正我用的静态链接
set(CMAKE_C_COMPILER /这里放下载解压的 Android NDK 的文件夹路径/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang) # 自行灵活调整,注意 armv7a 还是别的,也是自行调整
set(CMAKE_CPP_COMPILER /这里放下载解压的 Android NDK 的文件夹路径/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++) # 同上
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g") # 调试模式,没啥好说的
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g") # 同上
add_executable(android_server main.c)
修改完 CMakeLists.txt 后,把 Clion 默认的 build、debug 方案全部删除,按钮如下图(菜单左上角的加减号即为增删方案按钮):
进去把原来的全部删除后,新增方案:
其中只有一个点比较特别,端口 12345 怎么来的呢?要知道,这是 GDB 远程调试,也就是说,以我的项目为例,二进制跑在安卓 Arm64-v7a平台上,这里只能远程调试。而通过 adb shell gdbserver
绑定的安卓端口只在其本地,所以需要 adb forward
指令进行转发,这个 12345 就是我转发的 PC 上的端口,具体可看后面的脚本命令。
那么总结一下,流程应该是,本地配置好交叉编译参数,然后通过 NDK 提供的编译工具,编译生成可在安卓端运行的二进制文件。然后通过 gdbserver 进行远程调试。既然要使用 NDK 提供的编译工具,当然还要设置一下工具链。虽然根据文档在 cmake 文件里面设置了 C/C++ Compiler 路径,但是若点击 Clion 的 Build 按钮,实际测试未生效。所以如果想 Clion 的 Build 按钮生效,还需要参考下图所示新增安卓编译工具链的配置(编译器路径自然是和 cmake 里面的配置一样),并将其设置为默认。因为没找到项目级的设置方案,所以后面写 PC 代码的时候还要记得切回来,不然编译出来运行很自然会报错 —— 可执行文件格式错误。当然了,如果只使用后面提供的脚本命令,不使用 Clion 的 Build 按钮,这一步是可以省略的。
那么到现在,剩两个问题:
- 安卓端文件运行权限问题
- 端口怎么转发、 Clion 怎么连接
首先得跑起来,不然问题 2 不用看。经过测试,安卓端丢进一般路径如 /sdcard 是不能运行的,但是 /data/local/tmp/
目录下可以,还能调整执行权限(要是开了 Root 就不说了)。而问题 2 ,应该问题不大,查阅文档熟悉相关命令的使用就行。
运行与调试
对于上面的两个问题,我编写了两个简单的脚本,都在里面了,首先是编译后在安卓直接运行(可执行文件名为 android_server ):
#!/bin/bash
cd ./cmake-build-debug
cmake ..
make
adb push ./android_server /data/local/tmp/
adb shell chmod a+x /data/local/tmp/android_server
adb shell /data/local/tmp/android_server
第二个是编译后在安卓启动 gdbserver 等待调试器连接:
#!/bin/bash
adb forward tcp:12345 tcp:1234
cd ./cmake-build-debug
cmake ..
make
adb push ./android_server /data/local/tmp/
adb shell chmod a+x /data/local/tmp/android_server
adb shell gdbserver 0.0.0.0:1234 /data/local/tmp/android_server
留意上面第一条指令,进行了端口转发。然后,该指令可以多次重复运行不冲突,所以可以放在这里。于是乎,我们的测试运行与开启调试端口都 ok 了。测试一下:
其他问题
到这里其实还有两个问题:
- Clion 的代码提示环境得是安卓 NDK 的,不能是本机的
- 每次调试先运行脚本,再点一下 bug 图标很麻烦
对于问题 1 ,可以在 External Libraries 上右键,再点击 Reload Cmake Project ,然后提示和跟进都是 ok 的了。我这里截图抓不到 Clion 的悬浮菜单,可以看下图中对 struct stat
跟进的结果,已经是 NDK 里面的 sys/stat.h
了:
对于问题 2 ,理想的结果就是,我每次更新完代码, debug 之前自动调用一下,可惜的是我摸索了好一会儿,最后发现 Clion 貌似确实不支持设置 debug 前置动作。那么怎么解决呢?如果写过 Web 前端,使用诸如 Vue 之类的工具,每次更新完代码后相应的 Web 预览就自动刷新了。如果参考这种做法,就是监听文件动作,然后一旦有更新就执行对应脚本。如果自己写工具, Linux 平台参考 inotify API 即可,或者找一下称手的开源代码。我目前项目太小用不着,就不继续折腾了,到此为止。
请问有使用过windows clion远程linux andriod源码编译debug吗
抱歉我没有这么操作过,但是感觉应该行得通,你踩完坑可以分享一下哈哈。
受益匪浅的文章,但我按文章的步骤进行调试,仍没有调试成功,有以下问题。
环境:
Clion 2023.1.1
步骤:
Error Running 'AndroidRemote'
Incorrect run configuration Executable is not specified
请问一下,你是怎么解决这个问题的?
你好,我看了一下新版 Clion ,添加 Remote GDB Server 的时候,多了很多选项。这种改进挺好的,不用像之前那样自己写脚本了,也不用每次更新完代码, debug 之前调用一下脚本,相当于这些自己写的功能都内建了。
猜测遇到的问题就是,没有填完配置。需要自己选择编译 Target (也就是指定编译动作)和编译结果 Executable (也就是二进制文件)。也就是说随后点击 Debug 按钮的时候,如果代码有改动,会自动重新编译,并将编译出来的二进制文件,通过远程 gdb 连接自动上传和运行调试。
我在新版Clion上,按你上面的配置了Run Configure,然后点击debug,出现了报错:
Error running 'Android Remote'
Incorrect run configuration Executable is not specified
请问这是什么原因?
而且我也不太明白,点击debug后,理论上来说是启动一个进程。而脚本启动的是另外一个进程,那是怎么把这两个进程通过debug联系上的,不太明白,还请大佬赐教,谢谢
adb forward
指令,但不是重点。