我有一台 Android 设备(SAMSUNG Galaxy Tab Active2 SM-T397U),配有Exynos 7870 SoC。Exynos 7870使用 8 个实现Armv8-A 架构的Cortex A-53内核。不过,它不支持arm64-v8 ABI,也不清楚它支持哪种指令集。
有多个 Android 应用程序可以提取设备的系统 CPU 和 SoC 信息。
使用这样一个应用程序(AIDA64),它将指令集列为“64位ARM”,但支持的ABI仅列为armeabi-v7a和armeabi(下面的屏幕截图)。
另一个应用程序 (DroidInfo) 将 CPU 架构列为“ARMv7-A”,指令集也列为“armeabi-v7a”和“armeabi”(下面的屏幕截图)
DroidInfo 列出 SoC 模型和支持的指令集的屏幕截图
我的印象是,实现 v8a 架构的 CPU 核心能够利用armeabi-v8a ABI 来实现最优化的性能。
如果是这种情况,我想了解为什么该 SoC 仅支持 armeabi-v7a ABI 而不是 arm64-v8a ABI。ABI 上的 Android 开发人员页面针对 arm64-v8a ABI 进行了说明:
“此 ABI 适用于支持 AArch64 的基于 ARMv8 的 CPU。” …
标题可能不足以概述问题上下文。所以这里是描述:
SWIFT 编译过程 Swift 编译器经过以下步骤来编译 Swift 文件
根据苹果公司的说法,
IR生成(在lib/IRGen中实现)将SIL降低为LLVM IR,此时LLVM可以继续优化它并生成机器代码。
查询号 1 - 我们都知道编译器将我们的源代码转换为汇编语言,而汇编器(主要嵌入在操作系统中,至少 Swift 编译器中没有汇编器)将其转换为机器代码。因此,根据上面引用的语句,编译器中的 LLVM 将 LLVM IR 更改为机器代码。那么如果是这种情况,那么汇编器在 Swift 程序和执行中就没有任何作用了?
查询号 2 - Swift 中的 LLVM 将 LLVM IR 直接更改为机器代码。这意味着我编译的可执行二进制文件具有机器代码,而不是汇编代码。根据我的理解,机器代码不需要像汇编语言那样的任何特定的调用约定,而 ABI 就是关于调用约定、内存布局表示等,通过它们定义两个二进制文件之间的通信。那么,由于二进制可执行文件已经具有机器代码,ABI 会出现在哪里呢?
那么,是我遗漏了一些东西,还是苹果让它变得非常抽象?
在我的 Flutter 应用程序中,我尝试生成适用于所有设备(无论是 32 位还是 64 位)的 apk。
为此,我将以下行放入我的build.gradle文件中。但看起来它没有为所有架构生成libflutter.so 。
android {
compileSdkVersion 27
defaultConfig {
appId "com.google.example.64bit"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
ndk.abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64'
Run Code Online (Sandbox Code Playgroud)
如下图所示,libflutter.so仅存在于armeabi-v7a中,所有其他文件夹都缺少它。当我在 Google Play 上发布应用程序时,此问题会导致我的应用程序在启动时崩溃。
Run Code Online (Sandbox Code Playgroud)Exceptions java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/abc.xyz.idar-1/base.apk"],nativeLibraryDirectories=[/data/app/abc.xyz.idar-1/lib/arm64, /data/app/abc.xyz.idar-1/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64, /system/vendor/lib64, /product/lib64]]] couldn't find "libflutter.so"
我正在使用flutter build apk命令来生成 apk。
相关问题:标准布局和尾部填充
片段:
#include <iostream>
#include <type_traits>
struct A0
{
int a;
char c;
};
struct B0 : A0
{ char d; };
struct A1
{
int a;
private:
char c;
};
struct B1 : A1
{ char d; };
struct A2
{
private:
int a;
char c;
};
struct B2 : A2
{ char d; };
int main()
{
std::cout << std::is_pod<A0>::value << ' ' << sizeof(B0) << std::endl; // 1 12
std::cout << std::is_pod<A1>::value << ' ' << …Run Code Online (Sandbox Code Playgroud) 我经常忘记系统调用中每个参数需要使用的寄存器,每次我忘记时我都会访问这个问题。
x86_64 用户空间函数调用的整数/指针参数的正确顺序是:
%rdi、%rsi、%rdx、%rcx和%r8。%r9(可变参数函数采用 AL = FP 参数的数量,最多 8)
或者对于系统调用,%rax(系统调用调用号)和相同的参数,除了%r10代替%rcx.
记住这些寄存器而不是每次都用谷歌搜索这个问题的最佳方法是什么?
第 3.2.3 节中的x86_64 System V ABI指定函数调用的哪些参数进入哪些寄存器以及哪些被压入堆栈。我很难理解聚合分类的算法,它说(突出显示的是我的):
\n\n聚合(结构体和数组)和联合类型的分类工作原理如下:
\n\n
\n …- 如果对象的大小大于八个字节,或者包含未对齐的字段,则它具有 MEMORY 类。
\n- 如果 C++ 对象对于调用而言非常重要(如 C++ ABI13 中所指定),则它通过不可见引用传递(该对象在参数列表中被具有类 INTEGER 的指针替换)。
\n- 如果聚合的大小超过一个八字节,则每个字节将被单独分类。每个八字节都被初始化为 NO_CLASS 类。
\n- 对象的每个字段都被递归分类,以便始终考虑 两个字段。由此产生的类是根据八字节中字段的类来计算的: (a) 如果两个类相等,则这就是结果类。(b) 如果其中一个类是 NO_CLASS,则结果类是另一个类。(c) 如果其中一个类是 MEMORY,则结果是 MEMORY 类。(d) 如果其中一个类是 INTEGER,则结果是 MEMORY 类。是整数。(e) 如果类之一是 X87、X87UP、COMPLEX_X87 类,则使用 MEMORY 作为类。 (f) 否则使用类 SSE。
\n- 然后进行合并后清理: (a) 如果其中一个类是 MEMORY,则整个参数将在内存中传递。(b) 如果 X87UP 前面没有 X87,则整个参数将在内存中传递。(c) 如果聚合的大小超过两个八字节,并且第一个八字节是\xe2\x80\x99t SSE 或任何其他八字节是\xe2\x80\x99t SSEUP,则整个参数将在内存中传递。(d) 如果 SSEUP 前面没有 SSE 或 SSEUP,则转换为 SSE
\n
当前 (Linux) 版本的 SysV i386 ABI 需要在调用之前进行 16 字节堆栈对齐:
输入参数区域的末尾应在 16(32,如果 __m256 在堆栈上传递)字节边界上对齐。换句话说,当控制权转移到函数入口点时,值 (%esp + 4) 始终是 16 (32) 的倍数。
在 GCC 8.1 上,此代码在调用之前将堆栈与 16 字节边界对齐callee:( Godbolt )
| 来源 | # 字节 |
|---|---|
| 称呼 | 4 |
| 推送ebp | 4 |
| 子特别是,24 | 24 |
| 子特别, 4 | 4 |
| 推入eax | 4 |
| 推入eax | 4 |
| 推入eax | 4 |
| 全部的 | 48 |
在 GCC 8.2 及更高版本的所有版本上,它与 4 字节边界对齐:( Godbolt )
| 来源 | # 字节 |
|---|---|
| 称呼 | 4 |
| 推送ebp | 4 |
| 子特别,16 | 16 |
| 推入eax | 4 |
| 推入eax | 4 |
| 推入eax | 4 |
| 全部的 | 36 |
我想以编程方式修改 C++ 函数或变量的名称 - 以获取将出现在编译的目标文件中的符号名称。我正在使用 Linux 和 GCC。
现在,为什么这不是一件小事呢?例如typeid(foo).name()?因为这不符合您的要求:考虑以下程序:
#include <iostream>
extern int foo(int x) { return 0; }
extern double bar(int x) { return 1.0; }
int main()
{
std::cout << typeid(foo).name() << std::endl;
std::cout << typeid(bar).name() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
让我们看看这给了我们什么:
$ g++ -o a.o -O0 -c a.cpp
$ objdump -t a.o | egrep "(bar|foo)"
00000000000000dd l F .text 0000000000000015 _GLOBAL__sub_I__Z3fooi
0000000000000000 g F .text 000000000000000e _Z3fooi
000000000000000e g F .text 000000000000001b _Z3bari
$ ./a
FiiE
FiiE …Run Code Online (Sandbox Code Playgroud) 举个简单的例子:
struct has_destruct_t {
int a;
~has_destruct_t() {}
};
struct no_destruct_t {
int a;
};
int bar_no_destruct(no_destruct_t);
int foo_no_destruct(void) {
no_destruct_t tmp{};
bar_no_destruct(tmp);
return 0;
}
int bar_has_destruct(has_destruct_t);
int foo_has_destruct(void) {
has_destruct_t tmp{};
bar_has_destruct(tmp);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
foo_has_destruct代码生成稍差一些,因为析构函数似乎强制tmp进入堆栈:
foo_no_destruct(): # @foo_no_destruct()
pushq %rax
xorl %edi, %edi
callq bar_no_destruct(no_destruct_t)@PLT
xorl %eax, %eax
popq %rcx
retq
foo_has_destruct(): # @foo_has_destruct()
pushq %rax
movl $0, 4(%rsp)
leaq 4(%rsp), %rdi
callq bar_has_destruct(has_destruct_t)@PLT
xorl %eax, %eax
popq %rcx
retq
Run Code Online (Sandbox Code Playgroud)
https://godbolt.org/z/388K1EfYa
但是,考虑到析构函数是 1)普通内联的并且 2)空的,为什么需要这样的情况呢? …
c++ abi calling-convention micro-optimization compiler-optimization
我试图了解小代码模型中使用的 RIP 相对偏移量。也许互联网上有关此主题的唯一可用资源是: https: //eli.thegreenplace.net/2012/01/03/understanding-the-x64-code-models 但在这篇文章中也有一些事情不清楚。我使用这个简单的程序来了解一些事情:
// sample.cc
int arr[10] = {0};
int arr_big[100000] = {0};
int arr2[500] = {0};
int main() {
int t = 0;
t += arr[7];
t +=arr_big[6];
t += arr2[10];
return 0;
}
Run Code Online (Sandbox Code Playgroud)
汇编:g++ -c sample.cc -o sample.o
.text 部分的目标代码:( objdump -dS sample.o)
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
b: 8b 05 00 …Run Code Online (Sandbox Code Playgroud)