前言
最近接到了一个需求,需要开发一个Android的elf的demo。在Android Studio上折腾了一下,虽然代码提示很完美,但是部署调试不能令我满意(毕竟是拿来开发app的),所以折腾一下VSCode。
下面的配置所必须的插件:C/C++、CMake Tools、CodeLLDB
配置导入路径
这里使用的是比较新的NDK(27.2),旧版本的NDK路径可能不一样
首先使用NDK中的clang查询其导入文件的路径aarch64-linux-android22-clang++.cmd -E -x c++ - -v,查看#include <...> search starts here:部分
这里需要需要四个include路径,如下
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\aarch64-linux-android
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\c++\\v1
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\lib\\clang\\18\\include
其中include\\aarch64-linux-android是特定架构的头文件,这里如果想要在vscode中可以根据选择的配置来使用不同的头文件,则需要在.vscode目录中创建一个文件c_cpp_properties.json,添加如下内容(注意修改ndk路径)
{
"configurations": [
{
"name": "ARM (32-bit)",
"includePath": [
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\arm-linux-androideabi",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\c++\\v1",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\lib\\clang\\18\\include"
],
"defines": ["__ARM_ARCH_7A__", "ANDROID"],
"intelliSenseMode": "linux-clang-arm"
},
{
"name": "ARM64 (64-bit)",
"includePath": [
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\aarch64-linux-android",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\c++\\v1",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\lib\\clang\\18\\include"
],
"defines": ["__aarch64__", "ANDROID"],
"intelliSenseMode": "linux-clang-arm64"
},
{
"name": "x86_64 (64-bit)",
"includePath": [
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\x86_64-linux-android",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\c++\\v1",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\lib\\clang\\18\\include"
],
"defines": ["__x86_64__", "ANDROID"],
"intelliSenseMode": "linux-clang-x64"
}
,{
"name": "x86 (32-bit)",
"includePath": [
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\i686-linux-android",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\c++\\v1",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\lib\\clang\\18\\include"
],
"defines": ["__i386__", "ANDROID"],
"intelliSenseMode": "linux-clang-x86"
},
{
"name": "RISC-V (64-bit)",
"includePath": [
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\riscv64-linux-android",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include\\c++\\v1",
{ndkdir}\\toolchains\\llvm\\prebuilt\\windows-x86_64\\lib\\clang\\18\\include"
],
"defines": ["__riscv", "__riscv_xlen=64", "ANDROID"],
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
然后就可以在右下角选择不同的架构

此时代码提示已经正常了,代码也不会因为找不到头文件报错
CMake与构建
设定CMake路径
首先需要设定cmake的路径,这里使用的是AndroidSDK中的CMake。打开VS Code设置中的工作区(针对当前文件夹的单独设定),搜索找到CMake Path,设定为路径{SDKDir}\cmake\3.31.6\bin\cmake.exe
或者直接在文件夹下的.settings.json中添加一条"cmake.cmakePath": "{SDKDir}\\cmake\\3.31.6\\bin\\cmake.exe",也是一样的效果
CMake配置
首先在项目根目录下创建CMakeList.txt
cmake_minimum_required(VERSION 3.22.1)
# 设置 Android NDK 路径
set(ANDROID_NDK_HOME "path/to/ndk")
# 设置 CMake Toolchain 文件路径
set(CMAKE_TOOLCHAIN_FILE "${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake")
# 设置编译器路径
set(CMAKE_C_COMPILER "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd")
set(CMAKE_CXX_COMPILER "${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang++.cmd")
# Android 相关配置
set(ANDROID_PLATFORM android-21) # 最低支持的 API 级别
set(ANDROID_ABI arm64-v8a) # 目标架构
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(vtouchtest)
include_directories(".")
# 添加头文件路径
include_directories(include)
include_directories(${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/windows-x86_64/lib/clang/18/include) # clang的头文件
include_directories(${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/windows-x86_64/include/c++/4.9.x) # c++标准头文件
include_directories(${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include) # 系统头文件
# 架构特定的头文件
if(ANDROID_ABI STREQUAL "arm64-v8a")
include_directories(${ANDROID_SYSROOT}/usr/include/aarch64-linux-android)
elseif(ANDROID_ABI STREQUAL "armeabi-v7a")
include_directories(${ANDROID_SYSROOT}/usr/include/arm-linux-androideabi)
elseif(ANDROID_ABI STREQUAL "x86_64")
include_directories(${ANDROID_SYSROOT}/usr/include/x86_64-linux-android)
elseif(ANDROID_ABI STREQUAL "x86")
include_directories(${ANDROID_SYSROOT}/usr/include/i686-linux-android)
elseif(ANDROID_ABI STREQUAL "riscv64")
include_directories(${ANDROID_SYSROOT}/usr/include/riscv64-linux-android)
endif()
# 收集源文件
file(GLOB_RECURSE SOURCE_FILES "src/*.cpp" "src/*.c")
# 创建可执行文件
add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_FILES})
# 依赖库,按需添加
target_link_libraries(${CMAKE_PROJECT_NAME} "log")
target_link_libraries(${CMAKE_PROJECT_NAME} "z")
target_link_libraries(${CMAKE_PROJECT_NAME} "dl")
target_link_libraries(${CMAKE_PROJECT_NAME} "m")
此时就已经可以在终端中使用CMake进行构建了,命令如下
\path\to\sdk\cmake\3.31.6\bin\cmake.exe -G Ninja -DCMAKE_MAKE_PROGRAM="\path\to\sdk/cmake/3.31.6/bin/ninja.exe" .
\path\to\sdk\cmake\3.31.6\bin\cmake.exe --build .
但是这样并不优雅,所以还是接着配置vscode
这里使用Ctrl+Shift+P打开命令面板,搜索并执行CMake: Add Configure Preset ,输入名称,这会在项目目录下创建一个CMakePresets.json,并生成一个模板配置内容,这里补充CMAKE_MAKE_PROGRAM、CMAKE_TOOLCHAIN_FILE、ANDROID_ABI、ANDROID_PLATFORM,扩展为为如下内容
{
"name": "android-arm64-debug",
"displayName": "使用工具链文件配置预设",
"description": "设置 Ninja 生成器、版本和安装目录",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"cacheVariables": {
"CMAKE_MAKE_PROGRAM": "/Path/To/SDK/cmake/3.31.6/bin/ninja.exe",
"CMAKE_TOOLCHAIN_FILE": "/Path/To/NDK/build/cmake/android.toolchain.cmake",
"CMAKE_BUILD_TYPE": "Debug",
"ANDROID_ABI": "arm64-v8a",
"ANDROID_PLATFORM": "android-21",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
}
}
此时就可以删掉CMakeList.txt中的set(ANDROID_PLATFORM android-21)和set(ANDROID_ABI arm64-v8a)了,通过预设可以设定目标架构
这时在vscode的CMake侧边栏中,配置预设中选择刚才创建的预设

这会执行配置操作,也就是上面的\path\to\sdk\cmake\3.31.6\bin\cmake.exe -G Ninja -DCMAKE_MAKE_PROGRAM="\path\to\sdk/cmake/3.31.6/bin/ninja.exe" .操作
然后在生成处点击“生成”,即可编译处目标文件。或者在打开的cpp文件右上角,点击编译活动文件,也是一样的

部署与运行
这里说的部署,实际上就是发送到目标设备上,运行。
需要注意的是,受到vscode的限制,没什么比较好的方案在不编写新插件的情况下,在运行人物或者launch的时候支持多设备选择,以及处理多架构部署等等情况。
首先创建一个名为deploy.ps1的辅助脚本用于部署和运行elf,具体如下(其他系统对着改一下就好),放置在项目根目录的tools目录下
param (
[string]$BinaryPath="", # 二进制文件路径
[string]$DeviceTarget="/data/local/tmp", # 可选: 设备中目标二进制的路径
[string]$DeviceSerial="", # 可选: 特定设备序列号
[switch]$Deploy, # 是否部署
[switch]$Run, # 是否后运行
[switch]$Root, # 是否使用root运行
)
function Exit-WithError {
param (
[string]$Message,
[int]$ExitCode = 1
)
Write-Error $Message
exit $ExitCode
}
# 获取root权限并返回root方法
function Get-RootAccess {
param (
[string]$AdbCmd
)
$rootMethod = $null
# 检查su命令是否可用
$suCheck = Invoke-Expression "$AdbCmd shell 'which su'"
if ($suCheck -and $suCheck -ne "" -and $LASTEXITCODE -eq 0) {
Write-Host "Device has su binary available."
$rootMethod = "su"
return $rootMethod
}
# 尝试adb root
Write-Host "Attempting to restart adb as root..."
Invoke-Expression "$AdbCmd root"
if ($LASTEXITCODE -eq 0) {
# 等待adb重新连接
Start-Sleep -Seconds 2
# 验证adb是否真正以root运行
$idCheck = Invoke-Expression "$AdbCmd shell id"
if ($idCheck -match "uid=0\(root\)") {
Write-Host "ADB restarted in root mode successfully."
$rootMethod = "adb_root"
return $rootMethod
} else {
Write-Host "ADB root command didn't grant root privileges."
return $null
}
}
# 如果两种方法都失败,返回null
return $null
}
# 使用root权限执行命令
function Invoke-RootCommand {
param (
[string]$AdbCmd,
[string]$RootMethod,
[string]$Command
)
if ($RootMethod -eq "su") {
# 使用su执行命令
return Invoke-Expression "$AdbCmd shell `"su -c '$Command'`""
}
elseif ($RootMethod -eq "adb_root") {
# 直接执行命令(adb已经以root运行)
return Invoke-Expression "$AdbCmd shell `"$Command`""
}
else {
Exit-WithError "No root method available."
}
}
# 检查adb是否可用
try {
Invoke-Expression "adb version"
} catch {
Exit-WithError "ADB not found in PATH. Please install Android SDK Platform Tools or add it to PATH."
}
if ($Deploy) {
# 确保参数有效
if (-not $BinaryPath -or $BinaryPath -eq "") {
Exit-WithError "Missing binary file path!"
}
# 检查二进制文件是否存在
if (-not (Test-Path $BinaryPath)) {
Exit-WithError "Binary file does not exist: $BinaryPath"
exit 1
}
}
# 构建adb命令
$adbCmd = "adb"
if ($DeviceSerial) {
$adbCmd = "adb -s $DeviceSerial"
}
# 检查设备连接状态
$deviceCheck = Invoke-Expression "$adbCmd devices" | Select-String "device$"
if (-not $deviceCheck) {
if ($DeviceSerial) {
Exit-WithError "Device with serial '$DeviceSerial' not found or not authorized."
} else {
Exit-WithError "No connected devices found. Please connect a device or start an emulator."
}
}
# 检查是否连接了多个设备,但未指定设备序列号
if (-not $DeviceSerial) {
$deviceCount = @(Invoke-Expression "adb devices" | Select-String "device$").Count
if ($deviceCount -gt 1) {
$devicesList = Invoke-Expression "adb devices" | Select-String "device$"
$devicesInfo = "Connected devices:`n" + ($devicesList -join "`n")
Exit-WithError "Multiple devices detected but no specific device specified. Please use the -DeviceSerial parameter.`n$devicesInfo"
}
}
# 提取文件名
$fileName = Split-Path $BinaryPath -Leaf
if ($Deploy) {
# 执行部署
Write-Host "Deploying $fileName to device..."
Invoke-Expression "$adbCmd push `"$BinaryPath`" `"$DeviceTarget/$fileName`""
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Deployment failed! Check if target directory is writable." $LASTEXITCODE
}
# 设置执行权限
Write-Host "Setting execution permissions..."
Invoke-Expression "$adbCmd shell chmod 755 `"$DeviceTarget/$fileName`""
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Failed to set execution permissions." $LASTEXITCODE
}
}
# 是否需要运行
if ($Run) {
Write-Host "Running application on device..."
if ($Root) {
# 获取root权限
$rootMethod = Get-RootAccess -AdbCmd $adbCmd
if (-not $rootMethod) {
Exit-WithError "Device does not appear to be rooted. Cannot run as root."
}
Write-Host "Running with root privileges..."
$command = "cd $DeviceTarget && ./$fileName"
Invoke-RootCommand -AdbCmd $adbCmd -RootMethod $rootMethod -Command $command
} else {
Write-Host "Running with normal privileges..."
Invoke-Expression "$adbCmd shell `"cd $DeviceTarget && ./$fileName`""
}
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Application execution failed with exit code $LASTEXITCODE." $LASTEXITCODE
}
}
脚本由将部署和执行功能分开,后面配置起来更加灵活。
然后给CMake添加编译目标,由CMake调用脚本,这样免去了手动指定编译产物的麻烦
在CMakeList.txt下后方添加如下内容,如果需要指定设备则需要把设备代码填充到""中
# 配置文件后缀
if(CMAKE_HOST_WIN32)
set(SCRIPT_EXT ".ps1")
set(SCRIPT_CMD powershell)
set(SCRIPT_ARGS -ExecutionPolicy Bypass -File)
else()
set(SCRIPT_EXT ".sh")
set(SCRIPT_PREFIX "sh")
endif()
# 获取目标文件的路径 - 修复的部分
set(TARGET_NAME ${CMAKE_PROJECT_NAME}) # 确保这是您的实际目标名称
# 仅部署到设备
add_custom_target(deploy
COMMAND ${SCRIPT_CMD} ${SCRIPT_ARGS} "${CMAKE_CURRENT_SOURCE_DIR}/tools/deploy${SCRIPT_EXT}"
"$<TARGET_FILE:${TARGET_NAME}>"
"/data/local/tmp"
""
"-Deploy"
DEPENDS vtouchtest
COMMENT "部署到设备(不执行)"
VERBATIM
)
# 以普通权限执行
add_custom_target(run
COMMAND ${SCRIPT_CMD} ${SCRIPT_ARGS} "${CMAKE_CURRENT_SOURCE_DIR}/tools/deploy${SCRIPT_EXT}"
"$<TARGET_FILE:${TARGET_NAME}>"
"/data/local/tmp"
""
"-Run"
DEPENDS vtouchtest
COMMENT "在设备中执行"
VERBATIM
)
# 以root权限执行
add_custom_target(run_as_root
COMMAND ${SCRIPT_CMD} ${SCRIPT_ARGS} "${CMAKE_CURRENT_SOURCE_DIR}/tools/deploy${SCRIPT_EXT}"
"$<TARGET_FILE:${TARGET_NAME}>"
"/data/local/tmp"
""
"-Run"
"-Root"
DEPENDS vtouchtest
COMMENT "在设备中以root权限执行"
VERBATIM
)
最后在.vscode目录中添加一个tasks.json文件,添加上对应的任务即可
{
"version": "2.0.0",
"tasks": [
{
"type": "cmake",
"label": "CMake: configure",
"command": "configure",
"preset": "${command:cmake.activeConfigurePresetName}",
"detail": "CMake template configure task",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "cmake",
"label": "CMake: build",
"command": "build",
"preset": "${command:cmake.activeBuildPresetName}",
"detail": "CMake template build task",
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": [
"CMake: configure"
]
},
{
"type": "cmake",
"label": "CMake: install",
"command": "install",
"preset": "${command:cmake.activeBuildPresetName}",
"detail": "CMake template install task",
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": [
"CMake: build"
]
},
{
"type": "cmake",
"label": "CMake: clean",
"command": "clean",
"preset": "${command:cmake.activeBuildPresetName}",
"detail": "CMake template clean task",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "cmake",
"label": "CMake: clean rebuild",
"command": "cleanRebuild",
"preset": "${command:cmake.activeBuildPresetName}",
"detail": "CMake template clean rebuild task",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "部署",
"type": "cmake",
"command": "build",
"targets": [
"deploy"
],
"detail": "部署输出项目到目标设备",
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": [
"CMake: build"
]
},
{
"label": "仅运行",
"type": "cmake",
"command": "build",
"targets": [
"run"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "仅运行 (root)",
"type": "cmake",
"command": "build",
"targets": [
"run_as_root"
],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "构建并运行",
"type": "cmake",
"command": "build",
"targets": [
"run"
],
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": [
"部署"
]
},
{
"label": "构建并运行 (root)",
"type": "cmake",
"command": "build",
"targets": [
"run_as_root"
],
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": [
"部署"
]
}
],
}
现在就可以在编写完代码后,通过执行任务(具体来说,Task: Run Task中选择配置的任务),实现从构建到在设备上执行elf,一气呵成。
至于配置launch.json,无论是运行还是调试,都需要lldb,所以放到后面的步骤进行
调试
现在可以运行,就只剩下调试了。
对于调试来说,新版本的ndk只剩下lldb-server,已经不适用gdb了,所以这里也使用lldb-server。
lldv-server启停
首先需要做的就是管理lldb-server的启停。
对于启动来说,整体分为两步,即启动和端口转发。
辅助powershell脚本添加三个参数,同样每个功能单独配置
[switch]$StartLldb, # 是否开启lldb-server
[switch]$StopLldb, # 是否停止lldb-server
[int]$LldbPort=8086 # LLDB 端口,默认8086
对于停止来说,无非就是检查ps中有没有lldb-server,有的话干掉他。添加一个函数
# 停止LLDB服务器进程
function Stop-LldbServer {
param (
[string]$AdbCmd,
[bool]$FailOnError = $true
)
# 检查lldb-server是否在运行
$lldbRunning = Invoke-Expression "$AdbCmd shell 'ps | grep lldb-server | grep -v grep'"
if ($lldbRunning) {
Write-Host "Terminating lldb-server processes..."
# 检查lldb-server进程是否属于root
$isRootProcess = $lldbRunning -match "root"
# 根据进程所有者选择合适的权限进行终止
if ($isRootProcess) {
Write-Host "LLDB server is running as root, using root privileges to stop it..."
# 获取root权限
$rootMethod = Get-RootAccess -AdbCmd $AdbCmd
if (-not $rootMethod) {
if ($FailOnError) {
Exit-WithError "Device does not have root access. Cannot stop root LLDB server."
} else {
Write-Warning "Device does not have root access. Cannot stop root LLDB server."
return $false
}
}
# 使用root权限终止进程
$command = "killall lldb-server 2>/dev/null || true"
Invoke-RootCommand -AdbCmd $AdbCmd -RootMethod $rootMethod -Command $command
} else {
# 普通权限终止进程
Write-Host "Stopping LLDB server with normal privileges..."
Invoke-Expression "$AdbCmd shell 'killall lldb-server 2>/dev/null || true'"
}
# 验证进程是否真的被终止
Start-Sleep -Seconds 1
$stillRunning = Invoke-Expression "$AdbCmd shell 'ps | grep lldb-server | grep -v grep'"
if ($stillRunning) {
if ($FailOnError) {
Exit-WithError "Failed to stop lldb-server processes. Please restart your device or check permissions."
} else {
Write-Warning "Failed to stop lldb-server processes. Please restart your device or check permissions."
return $false
}
} else {
Write-Host "Successfully stopped lldb-server processes."
}
} else {
Write-Host "No running lldb-server processes found."
}
return $true
}
这里需要注意的是,如果lldb-server的用户是root,则也需要root用户关闭才行。
然后给关闭参数添加一个功能
if ($StopLldb) {
Write-Host "Stopping LLDB server..."
# 停止lldb-server进程
Stop-LldbServer -AdbCmd $adbCmd
# 移除端口转发
Write-Host "Removing port forwarding..."
Invoke-Expression "$adbCmd forward --remove tcp:$LldbPort 2>/dev/null" | Out-Null
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Failed to remove port forwarding." $LASTEXITCODE
}
Write-Host "LLDB server successfully stopped."
exit 0
}
对于开启来说,则需要,根据路径找到lldb-server,并使用设定的端口启动它。再启动之前会首先检查设定的端口是否被占用,并尝试删除现有的端口转发,最终启动lldb。
首先需要一个函数来检查设备端口和本地pc端口是否被占用,是否可以启用lldb-server和转发
# 检查端口是否被占用
function Test-PortInUse {
param (
[string]$AdbCmd,
[int]$Port
)
# 在设备上检查端口是否被占用
$netstatOutput = Invoke-Expression "$AdbCmd shell 'netstat -tunla'"
$netstatCheck = Write-Output $netstatOutput | Select-String '(?<=\s(?:\[?[\da-fA-F:\.]+]?:|0\.0\.0\.0:))$Port(?=\s|$)'
if ($netstatCheck) {
# 端口被占用
return $true
}
# 在主机上检查ADB转发的端口是否被占用
$portCheck = $null
# 使用正则表达式匹配本地端口
$portCheck = Invoke-Expression 'netstat -ano | Select-String -Pattern "(?:TCP|UDP)\s+.*\b$Port\b(?=\s|$)"'
return ($null -ne $portCheck -and $portCheck -ne "")
}
因为lldb是一个阻塞进程,所以还需要利用powershell的Start-Job非阻塞启动lldb-server,添加一个函数
# 使用PowerShell后台作业非阻塞启动lldb-server
function Start-LldbServer {
param (
[string]$AdbCmd,
[string]$DeviceTarget,
[int]$Port,
[bool]$UseRoot = $false
)
if ($UseRoot) {
# 获取root权限
$rootMethod = Get-RootAccess -AdbCmd $AdbCmd
if (-not $rootMethod) {
Exit-WithError "Device is not rooted. Cannot start lldb-server as root."
}
Write-Host "Starting lldb-server with root privileges..."
if ($rootMethod -eq "su") {
# 使用su运行lldb-server
$command = "su -c 'cd $DeviceTarget && ./lldb-server platform --server --listen *:$Port > /dev/null 2>&1 &'"
# 使用PowerShell后台作业启动
$job = Start-Job -ScriptBlock {
param($cmd, $shellcmd)
& cmd /c "$cmd shell `"$shellcmd`""
} -ArgumentList $AdbCmd, $command
} else {
# 使用adb root运行
$command = "cd $DeviceTarget && ./lldb-server platform --server --listen *:$Port > /dev/null 2>&1 &"
# 使用PowerShell后台作业启动
$job = Start-Job -ScriptBlock {
param($cmd, $shellcmd)
& cmd /c "$cmd shell `"$shellcmd`""
} -ArgumentList $AdbCmd, $command
}
} else {
# 普通权限运行
Write-Host "Starting lldb-server with normal privileges..."
$command = "cd $DeviceTarget && ./lldb-server platform --server --listen *:$Port > /dev/null 2>&1 &"
# 使用PowerShell后台作业启动
$job = Start-Job -ScriptBlock {
param($cmd, $shellcmd)
& cmd /c "$cmd shell `"$shellcmd`""
} -ArgumentList $AdbCmd, $command
}
# 给lldb-server启动一点时间
Start-Sleep -Seconds 1
# 检查作业状态并清理
if (Get-Job -Id $job.Id -ErrorAction SilentlyContinue) {
Write-Host "Cleaning up background job..."
Remove-Job -Id $job.Id -Force -ErrorAction SilentlyContinue
}
}
最后完成lldb启动功能
if ($StartLldb) {
Write-Host "Setting up LLDB server..."
# 检查设备上是否存在lldb服务器
$lldbPath = "$DeviceTarget/lldb-server"
$lldbCheck = Invoke-Expression "$adbCmd shell 'ls $lldbPath 2>/dev/null'"
$lldbExists = ($LASTEXITCODE -eq 0)
if (-not $lldbExists) {
Exit-WithError "lldb-server not found at $lldbPath on device. Please push it first."
}
# 端口未被占用,尝试移除现有的端口转发(如果存在)
Write-Host "Removing any existing port forwarding..."
try {
Invoke-Expression "$adbCmd forward --remove tcp:$LldbPort 2>&1" | Out-Null
} catch {
# 忽略错误,因为可能没有现有的端口转发
}
# 检查设备上端口是否被占用
Write-Host "Checking if port $LldbPort is available on device..."
$devicePortInUse = Test-PortInUse -AdbCmd $adbCmd -Port $LldbPort
if ($devicePortInUse) {
Exit-WithError "Port $LldbPort is already in use on the device. Cannot start lldb-server. Please choose a different port or stop the process using this port."
}
# 设置端口转发
Write-Host "Setting up port forwarding from localhost:$LldbPort to device:$LldbPort..."
Invoke-Expression "$adbCmd forward tcp:$LldbPort tcp:$LldbPort"
if ($LASTEXITCODE -ne 0) {
Exit-WithError "Failed to set up port forwarding." $LASTEXITCODE
}
# 使用非阻塞方式启动lldb-server
Start-LldbServer -AdbCmd $adbCmd -DeviceTarget $DeviceTarget -Port $LldbPort -UseRoot $Root
# 检查lldb-server是否成功启动
Start-Sleep -Seconds 1
$lldbRunning = Invoke-Expression "$adbCmd shell 'ps | grep lldb-server | grep -v grep'"
if (-not $lldbRunning) {
Exit-WithError "lldb-server failed to start properly."
}
Write-Host "LLDB server successfully started on port $LldbPort"
Write-Host "Port forwarding established: localhost:$LldbPort -> device:$LldbPort"
exit 0
}
给CMakeList添加上最硬的target(其实不加也行,之前是为了方便处理编译输出物路径,但是借助CMake Tool插件添加task更方便),并添加task即可,如下
# 启动lldb-server
add_custom_target(start_lldb
COMMAND ${SCRIPT_CMD} ${SCRIPT_ARGS} "${CMAKE_CURRENT_SOURCE_DIR}/tools/deploy${SCRIPT_EXT}"
""
"/data/local/tmp"
""
"-StartLldb"
COMMENT "Starting LLDB server on device"
VERBATIM
)
# root权限启动lldb-server
add_custom_target(start_lldb_root
COMMAND ${SCRIPT_CMD} ${SCRIPT_ARGS} "${CMAKE_CURRENT_SOURCE_DIR}/tools/deploy${SCRIPT_EXT}"
""
"/data/local/tmp"
""
"-StartLldb"
"-Root"
COMMENT "Starting LLDB server with root privileges"
VERBATIM
)
# 停止lldb-server
add_custom_target(stop_lldb
COMMAND ${SCRIPT_CMD} ${SCRIPT_ARGS} "${CMAKE_CURRENT_SOURCE_DIR}/tools/deploy${SCRIPT_EXT}"
""
"/data/local/tmp"
""
"-StopLldb"
COMMENT "Stopping LLDB server on device"
VERBATIM
)
{
"label": "启动LLDB服务器",
"type": "cmake",
"command": "build",
"targets": [
"start_lldb"
],
"group": "build",
"detail": "启动设备上的lldb服务器"
},
{
"label": "启动LLDB服务器 (Root)",
"type": "cmake",
"command": "build",
"targets": [
"start_lldb_root"
],
"group": "build",
"detail": "root权限下启动设备上的lldb服务器"
},
{
"label": "停止LLDB服务器",
"type": "cmake",
"command": "build",
"targets": [
"stop_lldb"
],
"group": "build",
"detail": "停止设备上的LLDB服务器"
}
启动和调试
到这一步终于开始配置launch了。因为使用的是lldb,所以这里使用插件codeLLDB。
因为目前这种方案的运行是基于cmake的target的,并且launch也不支持shell命令执行,也没找到办法创建一个空launch使其只执行TASK,所以这里就不创建执行的launch了,这个交给插件做更好。
这里针对调试,需要添加一个task来准备lldb环境。准确来说就是启动lldb-server,以及编译部署好产物。
添加两个task,分别执行构建、部署、lldb-server的停止和启动,非root和root创建两个
{
"label": "运行调试准备",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: build",
"部署",
"停止LLDB服务器",
"启动LLDB服务器"
],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"reveal": "silent",
"revealProblems": "onProblem",
"close": true
}
},
{
"label": "运行调试准备 (Root)",
"dependsOrder": "sequence",
"dependsOn": [
"CMake: build",
"部署",
"停止LLDB服务器",
"启动LLDB服务器 (Root)"
],
"group": "build",
"presentation": {
"reveal": "silent",
"revealProblems": "onProblem",
"close": true
}
}
然后利用codelldb插件创建launch,具体如下
{
"version": "0.2.0",
"configurations": [
{
"name": "远程调试",
"type": "lldb",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"cwd": "/data/local/tmp",
"initCommands": [
"platform select remote-android",
"platform connect connect://localhost:28086",
"platform settings -w /data/local/tmp",
"settings set target.inherit-env false", // 禁用继承环境变量
],
"stopOnEntry": false,
"preLaunchTask": "运行调试准备",
"sourceMap": {
"/data/local/tmp": "${workspaceFolder}"
},
"preTerminateCommands": [
"process kill", // 确保进程被正确终止
"process detach",
"platform disconnect connect://localhost:28086"
],
"postDebugTask": "停止LLDB服务器",
"presentation": {
"hidden": false,
"group": "调试",
"order": 1
}
},
{
"name": "远程调试 (Root)",
"type": "lldb",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"cwd": "/data/local/tmp",
"initCommands": [
"platform select remote-android",
"platform connect connect://localhost:28086",
"platform settings -w /data/local/tmp",
"settings set target.inherit-env false" // 禁用继承环境变量
],
"stopOnEntry": false,
"preLaunchTask": "运行调试准备 (Root)",
"sourceMap": {
"/data/local/tmp": "${workspaceFolder}"
},
"preTerminateCommands": [
"process kill", // 确保进程被正确终止
"process detach",
"platform disconnect connect://localhost:28086"
],
"postDebugTask": "停止LLDB服务器",
"presentation": {
"hidden": false,
"group": "调试",
"order": 2
}
}
]
}
需要注意的是,cwd需要填写的是目标设备的路径,而不是本地的。并且受到codelldb插件限制,其attach似乎只能选择当前电脑本地的pid,所以就只添加了调试。
至此,可以在vscode中愉快的开发Android的elf了。
后记
完整环境见WangONC / VSCode-Android-ELF-Template
配置到最后发现,配置文件的局限性很大,想变得更好用一点,自动化的配置更多东西非常困难,所以比较好的选择还是,直接开发一个插件…当作下一步的目标吧


