详解ARM64可执行程序的生成过程

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

ARM64可执行程序的生成过程

根据 ARM64 可执行程序生成的四个主要步骤:预处理、编译、汇编、链接,我们可以详细分解整个过程如下

1. 预处理(Preprocessing)

预处理是源代码文件在正式编译前的准备工作,由预处理器完成。其主要任务包括:

  • 宏替换:处理 #define 定义的宏,将代码中出现的宏替换为实际值。

  • 文件包含:处理 #include 指令,将所需的头文件内容直接插入代码中,确保所有引用的函数和变量声明都在同一文件中。

  • 条件编译:处理 #ifdef、#ifndef 等条件编译指令,以控制代码中不同部分的编译。

预处理后的输出仍然是代码文件,但没有任何宏、条件编译等指令,通常以 .i 或 .ii 作为扩展名。

2. 编译(Compilation)

编译器(如 GCC 或 Clang)将预处理后的代码文件转换为汇编代码,产生汇编语言表示的文件。此阶段包括以下子步骤:

  • 词法分析:将源代码转化为基本的语法单元(token),如变量名、运算符、关键字等。

  • 语法分析:将代码组织成抽象语法树(AST),根据编程语言的语法规则生成层次结构。

  • 语义分析:检查语法树的正确性,如类型检查、作用域检查等,确保代码符合语言语义。

  • 中间代码生成:编译器生成与平台无关的中间代码,方便后续优化。

  • 优化:编译器对中间代码进行优化,如消除冗余代码、进行循环优化等,以提升程序效率。

  • 生成汇编代码:编译器将优化后的中间代码转换为特定平台(如 ARM64)的汇编代码,通常输出 .s 文件。

编译阶段的最终输出是汇编代码文件,包含了基于 ARM64 指令集的指令。

3. 汇编(Assemble)

汇编器(如 GNU Assembler,as)将汇编代码文件(.s 文件)转换为机器代码,生成二进制的目标文件(.o 文件)。目标文件包含了二进制的机器指令,但符号引用还未解析,因此目标文件本身并非独立的可执行文件。

汇编阶段的主要工作包括:

  • 指令翻译:将汇编语言指令转换为 ARM64 指令集对应的二进制机器指令。

  • 符号表生成:记录所有函数和变量的符号地址,以便链接阶段使用。

  • 机器码生成:生成目标文件(.o),将每条指令翻译成可执行的机器码。

目标文件是 ARM64 可执行程序生成过程中不可或缺的中间文件。

4. 链接(Linking)

链接器(如 GNU Linker,ld)将一个或多个目标文件链接在一起,并解决外部依赖,生成最终的可执行文件。链接的过程分为静态链接和动态链接两种:

  • 静态链接:将程序所需的库代码直接嵌入到可执行文件中,生成一个完全自包含的文件。

  • 动态链接:生成的可执行文件依赖外部共享库(如 .so 文件),在程序运行时加载这些共享库。

链接阶段的关键步骤包括:

  • 符号解析:将不同目标文件中的符号(如函数和变量)解析为对应的内存地址,解决跨文件调用。

  • 重定位:调整目标文件中指令和数据的地址,使得所有模块可以在一个统一的地址空间中正常运行。

  • 生成可执行文件:链接器根据 ELF(Executable and Linkable Format)等格式生成最终的可执行文件,包含程序代码段、数据段、以及其他加载器所需的元信息。

最终生成的文件就是 ARM64 平台上的可执行文件,它包含所有代码和数据,并可以被加载器直接加载到内存中运行。

使用 NDK 中的 clang 生成 ARM64 程序

环境准备

首先先安装 NDK
https://developer.android.com/studio/projects/install-ndk?hl=zh-cn
image.png

clang 就存在 N D K / t o o l c h a i n s / l l v m / p r e b u i l t / NDK/toolchains/llvm/prebuilt/ NDK/toolchains/llvm/prebuilt/HOST_TAG/bin 目录下,$HOST_TAG 就你系统的架构,比如 windows 下就是 windows-x86_64
image.png
https://developer.android.com/ndk/guides/other_build_systems?hl=zh-cn

clang 适用于 C 编译,而 clang++ 专为 C++ 编译和链接 C++ 标准库设计。
image.png

打开命令行,把 clang 目录添加到环境变量

在 CMD 中

set PATH=D:\App\android\sdk\ndk\27.1.12297006\toolchains\llvm\prebuilt\windows-x86_64\bin;%PATH%

在 PowerShell 中

$env:PATH="D:\App\android\sdk\ndk\27.1.12297006\toolchains\llvm\prebuilt\windows-x86_64\bin;" + $env:PATH

编写源码

创建 hello.c,源码如下

#include <stdio.h>

int main() {
    printf("Hello, ARM64\n");
    return 0;
}

预处理(Preprocessing)

使用 -E 选项生成预处理后的文件:

  • -E:只进行预处理,不编译。

  • hello.i:生成的预处理文件,包含所有展开的宏和头文件内容。

aarch64-linux-android21-clang -E hello.c -o hello.i

hello.i 文件是预处理后的 C 文件,包含了展开的宏、包含的头文件和预处理指令。

1. 预处理器的行号指令

以 # 开头的行是预处理器的行号指令。

行号指令会指明代码行对应的源文件。编译器在遇到错误或警告时,可以正确地输出源文件名称,帮助开发者准确找到问题所在。例如,# 1 “hello.c” 表示以下代码行来自 hello.c 文件的第 1 行。
image.png

2. 头文件展开

#include <stdio.h> 会被完整展开,即 stdio.h 及其依赖的所有头文件内容被直接插入文件中。
image.png

image.png

3. 宏定义和类型声明

<stdio.h> 等头文件中定义的各种宏、类型和函数声明也在 hello.i 中展开。

image.png

image.png

4. 源代码内容

最后包含了源文件 hello.c 中的实际代码内容。
image.png

编译(Compilation)

使用 -S 选项生成汇编代码文件:

  • -S:将 C 源文件编译为汇编代码。

  • hello.s:输出的 ARM64 汇编代码文件。

aarch64-linux-android21-clang -S hello.c -o hello.s

汇编(Assemble)

使用 -c 选项将汇编代码转换为二进制目标文件:

  • -c:生成目标文件,不进行链接。

  • hello.o:生成的二进制目标文件,包含 ARM64 指令的机器码。

aarch64-linux-android21-clang -c hello.s -o hello.o

链接(Linking)

最终将目标文件链接成可执行文件:

  • hello.o:输入的目标文件。

  • -o hello_arm64:生成的最终可执行文件,适用于 ARM64 架构。

aarch64-linux-android21-clang hello.o -o hello_arm64

执行上述命令后,会得到以下文件:

  • hello.i:预处理文件

  • hello.s:汇编代码文件

  • hello.o:目标文件

  • hello_arm64:最终的 ARM64 可执行文件

推送到 android 设备中运行

将生成的 hello_arm64 可执行文件上传到 ARM64 设备(如 Android)并运行

adb push hello_arm64 /data/local/tmp/
adb shell chmod +x /data/local/tmp/hello_arm64
adb shell /data/local/tmp/hello_arm64

成功运行后,应在控制台上看到输出
截图.png

直接生成可执行程序

执行下面的命令自动完成预处理、编译、汇编和链接四个步骤,从源代码 hello.c 生成最终的可执行文件 hello_arm64。

aarch64-linux-android21-clang hello.c -o hello_arm64

ARM64 寄存器

1. 通用寄存器

ARM64 共有 31 个通用寄存器,分别为 X0 到 X30,它们都是 64 位(8 个字节)宽。每个寄存器的用途如下:

  • X0 - X7:用于函数调用的参数传递和返回值存储。通常,函数的前 8 个参数使用这几个寄存器传递,函数的返回值也存储在 X0 中。

  • X8:常被称为 “间接结果寄存器” 或 “平台寄存器”,用于存储系统调用号等特定平台数据。

  • X9 - X15:可以作为临时寄存器,由函数随意使用,通常不保存调用者的数据。

  • X16 - X17:又称 “跳板寄存器”,用于调用系统函数或执行函数跳转(如共享库调用)。

  • X18:线程寄存器,通常用于存储线程特定数据(TPIDR_EL1 寄存器也可用于类似目的)。

  • X19 - X28:保存调用者的数据,即调用方保存寄存器。函数调用时需要将这些寄存器保存到栈上,防止数据丢失。

  • X29(FP):帧指针(Frame Pointer),用于指向当前函数栈帧的基址。它使得调试器和编译器可以通过 X29 轻松访问栈帧内的局部变量和参数。。

  • X30(LR):链接寄存器(Link Register),保存函数调用的返回地址。

  • XZR/WZR:零寄存器(Zero Register)。读时总是返回 0,写时数据会被丢弃。

如果只使用下半部分(32 位),则用 W0 到 W30 表示。

2. 堆栈指针寄存器

SP:堆栈指针(Stack Pointer),指向当前栈顶的位置,负责栈的动态管理,随着函数调用和返回而变化。

在每次执行函数调用时,SP 会调整以分配或释放函数的栈帧空间。栈帧空间可以用于保存局部变量、传递参数和返回地址等。

以下面汇编代码为例

.text:0000000000025230                               var_18= -0x18     ; 局部变量 var_18 相对于 SP 的偏移量
.text:0000000000025230                               var_10= -0x10     ; 局部变量 var_10 相对于 SP 的偏移量
.text:0000000000025230                               var_8= -8         ; 局部变量 var_8 相对于 SP 的偏移量
.text:0000000000025230

.text:0000000000025230 FF 83 00 D1                   SUB             SP, SP, #0x20     ; 为当前栈帧分配 0x20 字节的空间
.text:0000000000025234 E0 0F 00 F9                   STR             X0, [SP,#0x20+var_8]    ; 将参数 X0 存储到 [SP - 0x8] 位置
.text:0000000000025238 E1 0B 00 F9                   STR             X1, [SP,#0x20+var_10]   ; 将参数 X1 存储到 [SP - 0x10] 位置
.text:000000000002523C E0 07 00 FD                   STR             D0, [SP,#0x20+var_18]   ; 将浮点数 D0 存储到 [SP - 0x18] 位置
.text:0000000000025240 E0 07 40 FD                   LDR             D0, [SP,#0x20+var_18]   ; 从 [SP - 0x18] 位置加载浮点数 D0
.text:0000000000025244 01 10 6E 1E                   FMOV            D1, #1.0                ; 将浮点数 1.0 赋给寄存器 D1
.text:0000000000025248 00 28 61 1E                   FADD            D0, D0, D1              ; D0 = D0 + D1,执行浮点数加法操作
.text:000000000002524C FF 83 00 91                   ADD             SP, SP, #0x20           ; 恢复栈帧,将 SP 加回 0x20 字节
.text:0000000000025250 C0 03 5F D6                   RET                                      ; 返回到调用函数

函数开始时的栈布局(分配栈帧后)

高地址
 ┌───────────────────┐
 │调用者的栈帧       │
 ├───────────────────┤
 │ D0 的值           │ <- SP + 0x20 - 0x18 (var_18)
 ├───────────────────┤
 │ X1 参数           │ <- SP + 0x20 - 0x10 (var_10)
 ├───────────────────┤
 │ X0 参数           │ <- SP + 0x20 - 0x08 (var_8)
 ├───────────────────┤
 │                  │
 └───────────────────┘ <- SP (当前函数栈帧的底部)
低地址

在ARM64的栈结构中,栈指针(SP)通常指向栈帧的底部(也就是较低地址的位置)。当函数分配栈帧时,比如 SUB SP, SP, #0x20,SP指针会向低地址方向移动,以创建一个新的栈帧空间。

在这个分配的栈帧中:

  • var_18 = -0x18 表示的是一个相对于当前 SP 的偏移量。例如,[SP, #0x20 + var_18] 实际上解读为 [SP - 0x18]。

  • 负偏移量是因为这些局部变量存储在 SP 之上(更高的地址),而 SP 的值始终指向栈帧的底部(低地址)。

在函数返回之前,ADD SP, SP, #0x20 恢复了堆栈指针,使SP回到调用函数之前的位置。这时的栈布局如下

高地址
 ┌───────────────────┐
 │调用者的栈帧       │ <- 恢复栈帧后的 SP
 ├───────────────────┤
 │                  │
 └───────────────────┘ 
低地址

3. 特殊寄存器

PC(程序计数器):指向当前正在执行的指令地址。

PSTATE:程序状态寄存器,包含条件标志、控制标志等(类似于 ARM 32 位架构中的 CPSR),影响程序的执行流程。

4. 浮点和 SIMD 寄存器

ARM64 还包含 32 个 128 位的浮点和 SIMD(Single Instruction, Multiple Data)寄存器。它们分别为:V0 - V31,用于存储浮点数、矢量数据。每个寄存器可以分成 128 位、64 位、32 位、16 位等不同大小的子寄存器,以适应不同的数据类型。

每个浮点/ SIMD 寄存器可以分为不同的宽度来操作:

  • Q0 - Q31:表示 128 位宽度。

  • D0 - D31:表示 64 位宽度(双精度浮点数)。

  • S0 - S31:表示 32 位宽度(单精度浮点数)。

  • H0 - H31 和 B0 - B31:分别表示 16 位和 8 位宽度,用于 SIMD 数据。

例如,下面的汇编代码中,FADD D0, D0, D1 执行的了浮点运算 ,结果存放在浮点寄存器 D0 中。而且由于这是一个浮点运算返回值,根据 ARM64 约定,浮点返回值保存在 D0 而不是 X0,所以无需将 D0 的结果移动到 X0。

.text:0000000000025230 FF 83 00 D1                   SUB             SP, SP, #0x20
.text:0000000000025234 E0 0F 00 F9                   STR             X0, [SP,#0x20+var_8]
.text:0000000000025238 E1 0B 00 F9                   STR             X1, [SP,#0x20+var_10]
.text:000000000002523C E0 07 00 FD                   STR             D0, [SP,#0x20+var_18]
.text:0000000000025240 E0 07 40 FD                   LDR             D0, [SP,#0x20+var_18]
.text:0000000000025244 01 10 6E 1E                   FMOV            D1, #1.0
.text:0000000000025248 00 28 61 1E                   FADD            D0, D0, D1
.text:000000000002524C FF 83 00 91                   ADD             SP, SP, #0x20 ; ' '
.text:0000000000025250 C0 03 5F D6                   RET

使用 GDB GEF 调试 C/C++ 程序

GDB(GNU Debugger)是一款功能强大的调试工具,用于调试 C、C++ 等语言的程序。

GDB 是一种命令行调试器,支持代码的断点设置、变量查看、内存查看、逐行跟踪执行等功能。然而,默认的 GDB 命令行界面和输出风格相对较为简单和基础,难以直观展示复杂数据结构或高级内存布局。

GEF 是 GDB Enhanced Features 的简称,它作为 GDB 的插件,为其添加了许多便捷功能和改进的界面。GEF 的功能涵盖内存管理、反汇编、寄存器状态查看、堆栈检查等。

GEF项目地址:https://github.com/hugsy/gef

1. 安装 GEF

先更新包管理器并安装 gdb 和 gcc

sudo apt update
sudo apt install gdb

使用以下命令来下载并安装 GEF

bash -c "$(wget https://gef.blah.cat/sh -O -)"

安装完成后,可以通过在终端中启动 GDB 来验证

cyrus@cyrus:/mnt/case_sensitive$ gdb
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
GEF for linux ready, type `gef' to start, `gef config' to configure
93 commands loaded and 5 functions added for GDB 12.1 in 0.00ms using Python engine 3.10
gef➤

2. 本地调试

获取当前系统架构

uname -m
x86_64

编写源码 hello_gdb_gef.c

#include <stdio.h>

int main() {
    printf("Hello, GDB GEF!\n");
    return 0;
}

安装 gcc 编译器

sudo apt install gcc

编译源码

gcc -o hello_gdb_gef hello_gdb_gef.c -no-pie

执行 hello_gdb_gef 输出正常

./hello_gdb_gef
Hello, GDB GEF!

使用 gdb 调试 hello_gdb_gef

# 启动 gdb
gdb -q hello_gdb_gef

# 在 main 函数下断点
b main

# 运行程序
run

# step over
ni

GEF中相关调试窗口介绍:

  • registers(寄存器窗口):显示当前 CPU 寄存器的内容。

  • stack(堆栈窗口):显示当前栈帧的内容。

  • code(代码窗口):显示当前执行位置附近的汇编指令。

  • arguments(参数窗口):显示当前函数调用的参数。

  • threads(线程窗口):显示当:前程序中的所有线程及其状态。

  • trace(跟踪窗口):显示调用堆栈的跟踪信息(函数调用的调用栈)。

image.png

3. 远程调试

由于 gdbserver 从 NDK r23 开始被移除(因为官方已经全面转向 LLDB 作为调试器)。可以从下面 Github 链接下载 gdbserver

https://github.com/topjohnwu/FrankeNDK/blob/master/prebuilt/android-arm64/gdbserver/gdbserver

把 gdbserver 推送到 android 设备并增加执行权限

# 把 gdbserver 推送到设备 /data/local/tmp 目录下
adb push "D:\App\android\sdk\ndk\android-arm64\gdbserver" /data/local/tmp/gs

# 添加可执行权限
adb shell chmod +x /data/local/tmp/gs

启动 gdbserver

# 启动 gdbserver 在自定义的 4321 端口调试 hello_arm64
adb shell /data/local/tmp/gs :4321 /data/local/tmp/hello_arm64

Process /data/local/tmp/hello_arm64 created; pid = 10866
Listening on port 4321

并通过 adb 转发设备端口到转本地

adb forward tcp:4321 tcp:4321

启动 gdb 并链接 gdb server

gdb
gef➤  target remote 127.0.0.1:4321

接下来就可以开始调试了。

3. 常用动态调试指令

# 反汇编 main 函数
disassemble main

# 在 main 函数入口下断点
break main
# 或者
b main

# 在指定地址下断点
b *0x401136

# 显示当前断点列表
info b

# 运行程序
run 
        
# 继续执行
c

# 单步步入(汇编层)
si

# 单步步入(源码层)
s

# 单步步过(汇编层)
ni

# 单步步过(源码层)
n

# 取指定地址下的值并以十六进制打印
p/x *0x402004

# 打印字符串
x/s 0x402004

# 退出调试 
exit

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907009.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DB-GPT系列(二):DB-GPT部署(镜像一键部署、源码部署)

一、简介 DB-GPT 是一个开源项目&#xff0c;其将大语言模型 LLM 与数据库紧密结合。该项目主要致力于探索如何让预训练的大规模语言模型&#xff08;例如 GPT&#xff09;能够直接与数据库进行交互&#xff0c;从而生成更为准确且信息丰富的回答。 DB-GPT部署后能否直接使用…

升序数组两两不相等

题目&#xff1a;给定一个排好升序的数组A[1],A[2],… A[n]&#xff0c;其元素的值两两都不相等。请设计一个高效算法&#xff0c;找出其中所有A[]i的下标&#xff0c;并分析其复杂度。 算法分析&#xff1a;一个升序且值都不相等的数组&#xff0c;如果第一个数大于右下标&…

基于vue框架的的乐守护儿童成长记录系统b65tg(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,成长指标,疫苗接种,学业档案,课外活动,旅游经历,交流论坛 开题报告内容 基于Vue框架的乐守护儿童成长记录系统开题报告 一、研究背景与意义 随着科技的飞速发展和家庭对子女成长关注度的不断提升&#xff0c;如何科学、系统地记…

VSCode 设置环境变量(WSL 2)

环境&#xff1a;openEuler、Windows 11、WSL 2、python 3.12.3 背景&#xff1a;使用vscode连接Windows 的Linux子系统&#xff0c;开发python项目&#xff0c;获取环境变量失败 时间&#xff1a;20241029 说明&#xff1a;使用os.environ获取不到变量&#xff0c;设置/etc…

使用Git LFS管理大型文件

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Git LFS管理大型文件 引言 Git LFS 简介 安装 Git LFS 安装 Git 安装 Git LFS 配置 Git LFS 初始化 Git 仓库 指定需要使用…

RHCE的练习(10)

实验1&#xff1a;反向解析 准备工作 [rootserver ~]# setenforce 0[rootserver ~]# systemctl stop firewalld# 服务端安装bind软件 [rootserver ~]# dnf install bind -y DNS配置 第一步&#xff1a;服务端操作&#xff0c;编辑bind主配置文件 [rootbogon ~]# cat /e…

Redis-结构化value对象的类型

文章目录 一、Redis的结构化value对象类型的介绍二、Redis的这些结构化value对象类型的通用操作查看指定key的数据类型查看所有的key判断指定key是否存在为已存在的key进行重命名为指定key设置存活时间pexpire与expire 查看指定Key的存活时间为指定key设置成永久存活 三、Redis…

产品结构设计(六):结构设计全过程

参考引用 产品结构设计实例教程 1. ID 图及 PCB 堆叠分析 1.1 产品说明及相关资料 1、新产品开发指令单 2、ID 图 3、产品功能规格书 1.2 ID 图分析 ID&#xff08;Industrial Design&#xff0c;工业设计&#xff09;是以工业产品为主要对象&#xff0c;综合运用工学、…

Apache Dubbo (RPC框架)

本文参考官方文档&#xff1a;Apache Dubbo 1. Dubbo 简介与核心功能 Apache Dubbo 是一个高性能、轻量级的开源Java RPC框架&#xff0c;用于快速开发高性能的服务。它提供了服务的注册、发现、调用、监控等核心功能&#xff0c;以及负载均衡、流量控制、服务降级等高级功能。…

webGlL变量的声明与使用

抢先观看&#xff1a; 变量的声明格式&#xff1a;<存储限定符><类型限定符><变量名> 存储限定符&#xff1a;const, attribute, uniform, varying, buffer。 类型限定符&#xff1a;void, bool, int, float, double, vec2, vec3, vec4, mat2, mat3, mat4, s…

免费送源码:Java+CSS+springboot Springboot高校医务室管理系统 计算机毕业设计原创定制

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…

CDN加速实战:使用七牛云CDN加速阿里云OSS资源访问

今天是双11搞活动,在阿里云1元注册了个域名,想着在学CDN,想使用CDN做个加速项目,但是阿里的要收费,上网查了下七牛云的不收费,想着将七牛云的CDN结合阿里的DNS做个访问加速,刚好看到了阿里的一个文章,照着改了改,实践成功了。 阿里文章:使用CDN加速OSS资源访问_对象…

SpringMVC执行流程(视图阶段JSP、前后端分离阶段)、面试题

目录 1.SpringMVC执行流程分为以下两种 2.非前后端分离的SpringMVC的执行流程 3.前后端分离的项目SpringMVC执行流程 4. 面试题 1.SpringMVC执行流程分为以下两种 2.非前后端分离的SpringMVC的执行流程 流程图&#xff1a; 更加生动的描述&#xff1a; DisPatcherServlet…

笔记本电脑买i7还是i9?i7和i9处理器区别详细介绍

i7和i9处理器都是英特尔&#xff08;Intel&#xff09;公司生产的高性能处理器&#xff0c;但它们有一些显著的区别。为了帮助你做出明智的选择&#xff0c;下面我们详细介绍一下i7和i9处理器的区别&#xff0c;以及如何根据你的需求来选择合适的处理器。 一、i7处理器的特点…

51c大模型~合集12

我自己的原文哦~ https://blog.51cto.com/whaosoft/11564858 #ProCo 无限contrastive pairs的长尾对比学习 , 个人主页&#xff1a;https://andy-du20.github.io 本文介绍清华大学的一篇关于长尾视觉识别的论文: Probabilistic Contrastive Learning for Long-Tailed Visua…

【数据结构篇】探索堆的算法的巧妙

须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1f;别忘了点…

智能家居10G雷达感应开关模块,飞睿智能uA级别低功耗、超高灵敏度,瞬间响应快

在当今科技飞速发展的时代&#xff0c;智能家居已经逐渐成为人们生活中不可或缺的一部分。从智能灯光控制到智能家电的联动&#xff0c;每一个细节都在为我们的生活带来便利和舒适。而在众多智能家居产品中&#xff0c;10G 雷达感应开关模块以其独特的优势&#xff0c;正逐渐成…

如何使用VBA识别Excel中的“单元格中的图片”(2/2)

Excel 365升级了新功能&#xff0c;支持两种不同的插入图片方式&#xff1a; 放置在单元格中&#xff08;Place in cell&#xff09;&#xff0c;新功能&#xff0c;此操作插入的图片下文中简称为单元格中的图片。放置在单元格上&#xff08;Place over cell&#xff09;&…

Nature|用于无线监测颅内信号的植入式柔性超声波传感器(柔性传感/健康监测/植入式电子/水凝胶)

华中科技大学臧剑锋(Jianfeng Zang)、华中科技大学同济医学院附属协和医院姜晓兵(Xiaobing Jiang)和新加坡南洋理工大学陈晓东(Xiaodong Chen)团队,在《Nature》上发布了一篇题为“Injectable ultrasonic sensor for wireless monitoring of intracranial signals”的论…

FlaskFastAPIgunicornunicorn并发调用

Flask VS. FastAPI Flask和FastAPI是Python中两种流行的Web框架&#xff0c;它们各自具有不同的特点和适用场景。以下是它们之间的一些主要区别&#xff1a; 1. 框架类型 Flask&#xff1a;Flask是一个轻量级的微框架&#xff0c;适合构建小型到中型的Web应用。它灵活且易于扩展…