版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
内联汇编
Android 内联汇编非常适用于 ARM 架构的性能优化和底层操作,通常用于加密、解密、特定指令优化等领域。
1. 基础语法
内联汇编在 C/C++ 代码中通过 asm 或 asm 关键字进行声明,格式如下
asm (“汇编指令” : 输出操作数 : 输入操作数 : 破坏描述符);
详细说明:
-
汇编指令:这是我们想要执行的汇编代码,通常是 ARM 或 ARM64 指令。
-
输出操作数:指定汇编代码的输出结果如何映射到 C++ 变量。
-
输入操作数:指定传递给汇编代码的输入。
-
破坏描述符:用于告诉编译器哪些寄存器或内存位置将被汇编代码修改,以避免编译器优化引起的问题。
2. 占位符
占位符用于在汇编指令中插入 C++ 变量,格式为 %0、%1 等,对应输出和输入操作数的顺序。
例如
int x = 10, y = 20, result;
asm("add %0, %1, %2" : "=r"(result) : "r"(x), "r"(y));
上面的代码将 x 和 y 相加并将结果存入 result。
3. 输出操作数和输入操作数
=r 表示输出操作数是一个通用寄存器类型。
r 表示输入操作数是一个寄存器类型。
例如
int a = 5, b = 3, result;
asm("mul %0, %1, %2" : "=r"(result) : "r"(a), "r"(b));
这段代码在 ARM 架构中将 a 和 b 相乘,结果存入 result。
4. 破坏描述符
破坏描述符(clobber)用于告诉编译器哪些寄存器或内存位置将被汇编代码修改,以避免编译器优化引起的问题。
常用的描述符包括:
-
“cc”:表示汇编代码将更改条件代码寄存器。
-
“memory”:表示汇编代码可能更改内存内容。
例如
asm("mov %0, #0\n"
"cmp %1, %2\n"
"moveq %0, #1"
: "=r"(result)
: "r"(a), "r"(b)
: "cc");
这里 cc 表示条件标志寄存器会被更改,编译器需要考虑这一点。
5. 使用 volatile
在汇编指令前添加 volatile 关键字,确保编译器不会优化或重新排序该段汇编代码。
例如
asm volatile ("nop"); // 表示这是一个空操作,编译器不会优化掉
6. 指针操作
内联汇编还可以使用指针操作对内存内容进行直接操作。例如
int value = 42;
int* ptr = &value;
asm("ldr %0, [%1]" : "=r"(value) : "r"(ptr) : "memory");
这里 ldr 从 ptr 指向的内存地址加载值到 value 中。
7. 示例:简单加法操作
以下是一个在 Android ARM 架构中使用内联汇编执行加法的示例
int a = 10, b = 20, sum;
asm("add %0, %1, %2" : "=r"(sum) : "r"(a), "r"(b));
这段代码执行 a + b 并将结果存储在 sum 中。
多行汇编可以使用反斜杠 \n 进行换行。例如,计算两个数的平方和
int x = 3, y = 4, result;
asm(
"mul %0, %1, %1\n" // result = x * x
"mla %0, %2, %2, %0" // result += y * y (multiply-accumulate)
: "=r"(result)
: "r"(x), "r"(y)
);
Android Studio 汇编开发
首先创建 Native C++ 工程
创建 Activity,声明 native 函数,点击按钮调用 native 层用汇编实现的加密/解密方法并打印返回结果。
package com.cyrus.example.assembly
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.cyrus.example.R
/**
* 内联汇编
*/
class AssemblyActivity : AppCompatActivity() {
// 加载 native 库
init {
System.loadLibrary("assembly-lib");
}
// 通过内联汇编实现的加密函数
external fun encryptString(input: String?): String
// 通过内联汇编实现的解密函数
external fun decryptString(input: String?): String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_assembly) // 更新布局文件名
// 原始字符串
val input = "Hello, 内联汇编!"
// 加密按钮
val encryptButton = findViewById<Button>(R.id.button_encrypt)
encryptButton.setOnClickListener { view: View? ->
// 调用 C++ 方法获取加密后的字符串
val encrypted = encryptString(input)
// 打印原字符串和加密后的字符串
val message = "Original: $input\nEncrypted: $encrypted"
Toast.makeText(this@AssemblyActivity, message, Toast.LENGTH_LONG).show()
}
// 解密按钮
val decryptButton = findViewById<Button>(R.id.button_decrypt)
decryptButton.setOnClickListener { view: View? ->
// 调用 C++ 方法获取加密后的字符串
val encrypted = encryptString(input)
val decrypted = decryptString(encrypted)
// 打印加密字符串和解密后的字符串
val message = "Encrypted: $encrypted\nDecrypted: $decrypted"
Toast.makeText(this@AssemblyActivity, message, Toast.LENGTH_LONG).show()
}
}
}
创建 assembly-lib.cpp,编写内联汇编代码
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOG_TAG "assembly-lib.cpp"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_cyrus_example_assembly_AssemblyActivity_encryptString(JNIEnv *env, jobject /* this */,
jstring input) {
const char *inputStr = env->GetStringUTFChars(input, nullptr);
std::string encryptedStr(inputStr);
// 获取输入字符串的 Unicode 码点
const jchar *inputChars = env->GetStringChars(input, nullptr);
jsize length = env->GetStringLength(input);
// 创建加密后的字符串
jchar *encryptedChars = new jchar[length];
for (jsize i = 0; i < length; i++) {
jchar c = inputChars[i];
// 使用内联汇编对每个 Unicode 字符的值加 3,实现加密
asm volatile (
"add %0, %1, #3\n" // 每个字符的 Unicode 值加 3
: "=r"(c) // 输出到 c
: "r"(c) // 输入 c
);
encryptedChars[i] = c;
}
// 释放输入字符串的内存
env->ReleaseStringChars(input, inputChars);
jstring encryptedString = env->NewString(encryptedChars, length);
// 释放加密字符串的内存
delete[] encryptedChars;
return encryptedString;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_cyrus_example_assembly_AssemblyActivity_decryptString(JNIEnv *env, jobject /* this */,
jstring input) {
const char *inputStr = env->GetStringUTFChars(input, nullptr);
std::string decryptedStr(inputStr);
// 获取输入字符串的 Unicode 码点
const jchar *inputChars = env->GetStringChars(input, nullptr);
jsize length = env->GetStringLength(input);
// 创建解密后的字符串
jchar *decryptedChars = new jchar[length];
for (jsize i = 0; i < length; i++) {
jchar c = inputChars[i];
// 使用内联汇编对每个 Unicode 字符的值减 3,实现解密
asm volatile (
"sub %0, %1, #3\n" // 每个字符的 Unicode 值减 3
: "=r"(c) // 输出到 c
: "r"(c) // 输入 c
);
decryptedChars[i] = c;
}
// 释放输入字符串的内存
env->ReleaseStringChars(input, inputChars);
jstring decryptedString = env->NewString(decryptedChars, length);
// 释放解密字符串的内存
delete[] decryptedChars;
return decryptedString;
}
配置 CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
find_library( # Sets the name of the path variable.
log-lib
# Specifies the NDK library that you want CMake to locate.
log)
add_library( # 设置库的名称
assembly-lib
# 设置库的类型
SHARED
# 设置源文件路径
assembly-lib.cpp)
target_link_libraries( # 将 log 库链接到目标库
assembly-lib
${log-lib})
运行测试
兼容不同的 CPU 架构
在 Android 开发中,编写兼容不同架构的内联汇编代码时,可以通过条件编译来处理不同的指令集。
由于 Android 设备可能使用不同的 CPU 架构(如 ARM、ARM64、x86 和 x86_64),使用条件编译和 NDK 的特性,我们可以让代码适配不同的 CPU 架构。
1. 使用条件编译判断架构
通过 #ifdef 和 #if defined(…) 指令,判断当前编译架构并编写相应的内联汇编代码。
extern "C"
JNIEXPORT jint JNICALL
Java_com_cyrus_example_assembly_AssemblyActivity_addNumbers(JNIEnv *env, jobject, jint a,
jint b) {
int result;
#if defined(__aarch64__)
// ARM64 内联汇编版本
asm volatile (
"add %w[result], %w[val1], %w[val2]\n" // 执行加法
: [result] "=r" (result) // 输出操作数
: [val1] "r" (a), [val2] "r" (b) // 输入操作数
);
#elif defined(__arm__)
// ARM 32-bit 内联汇编版本
asm volatile (
"add %[result], %[val1], %[val2]\n" // 执行加法
: [result] "=r" (result) // 输出操作数
: [val1] "r" (a), [val2] "r" (b) // 输入操作数
);
#elif defined(__i386__)
// x86 内联汇编版本
asm volatile (
"addl %[val1], %[val2]\n"
"movl %[val2], %[result]\n" // 使用32位 x86 指令完成加法
: [result] "=r" (result)
: [val1] "r" (a), [val2] "r" (b)
);
#elif defined(__x86_64__)
// x86_64 内联汇编版本
asm volatile (
"addq %[val1], %[val2]\n"
"movq %[val2], %[result]\n" // 使用64位 x86 指令完成加法
: [result] "=r" (result)
: [val1] "r" (a), [val2] "r" (b)
);
#else
// 如果架构不支持,使用 C++ 代码实现
result = a + b;
#endif
LOGI("Result of addition: %d", result);
return result;
}
2. 使用 abiFilters 指定编译目标
使用 abiFilters 来指定不同的 ABI,以便编译每个架构的共享库
// build.gradle
android {
defaultConfig {
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
}
build 出来的 apk 会包含不同 CPU 架构下的 so
最后,在不同 CPU 架构下的设备下运行测试正常