汇编语言学习

想要理解栈溢出的最基本原理,汇编和栈是必不可少的,不然想我之前学了也是白学,原理都不知道

一、准备

1.安装gcc

sudo apt-get  build-dep  gcc

这里显示版本不对,我用的是国内镜像源,需要换一下配置

sudo nano /etc/apt/sources.list

依次在下面加入以下内容

deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-updates main restricted universe multiverse
deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ noble-backports main restricted universe multiverse

然后再次安装试试

成功了,记得配置完后要更新软件源,命令如下

sudo apt-get update

2.汇编语言与pwn

1>关系

PWN需要对逆向了解,调试模式中会产生大量的汇编代码解读,需要对汇编有一定的基础

2>常用知识点

8086的CPU通用寄存器均为16位,可以存放两个字节,AX,BX,CX,DX四个寄存器存一般性数据

ah:AX高位寄存器 al:AX地位寄存器

字节(byte):8位bit,存在8位寄存器中

字(word):两个字节,16位bit,存于16位通用寄存器中

段寄存器:CS/DS/SS/ES

段寄存器的由来:已知寄存器位数16,而地址总线为20位,要利用16位寄存器来访问20位的地址时,需要进行(16*段寄存器+偏移地址)=地址总线 的方式来进行寻址。

CS与IP寄存器:指示当前CPU要读取的指令地址,CS:代码段寄存器  IP:指令指针寄存器

设CS中为M,IP为N,8086CPU将从Mx16+N读取指令开始执行,也称为:CS:IP指向内容作为指令执行。

指令运行的全过程:

CS:IP通过地址加法器,经过输入输出电路、20位地址总线进入内存找到地址,提取机器指令,返回到指令缓冲器及执行控制器,执行指令后根据指令长度,IP加值长度。

jump指令:jump CS:IP 相当于执行CS:IP的命令

二、hello word

写一个简单的c语言文件

#include<stdio.h>

int main(){
    printf("hello world");
    return 0;
}

用 GCC 等编译器将其编译为二进制文件,然后就可以在计算机上运行了,而在这个编译过程中,就有一个中间步骤:将 C 语言源码转化为汇编语言

操作如下

gcc -S 1.c -o 1.s -masm=intel

c 语言源码 1.c 会被编译,并输出等价的 intel 语法的汇编语言源码在 1.s 中

可以打开看一下,然后把不需要的代码删除

.LC0:
    .string "hello world"
main:
    lea rdi, .LC0[rip]
    mov eax, 0
    call    printf@PLT
    mov eax, 0
    ret

只留下这些

.LC0 可以看做是一个常量,其内容是字符串的 hello world,而下面的 main: 就是 main 函数了

这里补充一下main函数中用到的这些指令

lea:计算有效地址,在这里,我们可以看做是将 .LC0[rip] 的地址,即 hello world 字符串的地址转移至 rdi 寄存器中,这里提到了寄存器,就是一个位于 CPU 内的储存结构,里边可以存一些变量啥的,而这里的 rdi 寄存器就是第一个参数的寄存器

rdi寄存器:是通用寄存器中的一个,用于存储函数参数的值。在函数调用时,参数值会被传递到%rdi寄存器中,供被调用的函数使用,在函数调用过程中,%rdi寄存器起到了传递参数的作用。当一个函数被调用时,函数的参数值会被依次放入%rdi、%rsi、%rdx、%rcx、%r8和%r9这六个寄存器中(如果参数个数超过六个,就会使用堆栈传递参数)

mov eax, 0:mov 是 move 的缩写,这里的意思也就是将 0 复制(转移)到 eax 寄存器中, eax 这个寄存器也比较特殊,它是返回值寄存器,任何函数的返回值都会被储存在这个寄存器中,举个例子,在我们 call printf 以后,eax 寄存器内的值就会变成 printf 的返回值,而我们 main 函数在返回的时候是有一个 return 0 的,所以在 ret(return 返回)指令前,有一条 mov eax, 0 的指令,这样在 return 的时候才能保证我们的返回值是 0,至于前面那个 mov eax, 0 其实没啥用

eax寄存器:"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器

call printf :调用printf函数

三、函数调用流程和栈

1.栈

写一个稍微复杂一点的程序

#include<stdio.h>

int add(int a, int b){
    return a + b;
} 

int main(){
    printf("%d", add(2, 3));
    return 0;
}

和刚才一样用gcc编译

打开查看并删除不需要的代码,只留下面这些

add:
        push    rbp
    mov rbp, rsp
    mov DWORD PTR -4[rbp], edi
    mov DWORD PTR -8[rbp], esi
    mov edx, DWORD PTR -4[rbp]
    mov eax, DWORD PTR -8[rbp]
    add eax, edx
    pop rbp
    ret
main:
    push    rbp
    mov rbp, rsp
    mov esi, 3
    mov edi, 2
    call    add
    mov esi, eax
    lea rdi, .LC0[rip]
    mov eax, 0
    call    printf@PLT
    mov eax, 0
    pop rbp
    ret

接下来先看add函数起什么作用

将 edi 寄存器内的值通过 mov 指令复制到了 DWORD PTR -4[rbp](这里暂时不知道是什么),并且将 esi 寄存器内的值复制到了 DWORD PTR -8[rbp](与上面一样),又将这两个地方的值转移回了 edx 寄存器和 eax 寄存器(上面说了eax寄存器是个累加寄存器,而edx寄存器总是被用来放整数除法产生的余数),这里的edi和esi其实是 add 函数第一个参数和第二个参数,但是上面说了第一个参数是rdi,二者其实是一样的(edi 其实就是 rdi ,只不过他们的范围不太一样, edi 寄存器的范围为 rdi 寄存器的低 32 位,而 rdi 寄存器是 64 位的,同样的,还有 di 寄存器和 dil 寄存器,分别表示 rdi 寄存器的低 16 位与低 8 位)

这里附上一张寄存器的图,包含了各个寄存器

接着解释add函数,由于两个参数都是 int 类型的,只占 32 位,所以使用了 edi 寄存器和 esi 寄存器,先将两个参数分别复制到了两个不知道是什么的地方,然后又将他们复制到了 eax 寄存器和 edx 寄存器中,现在假设我们调用这个函数的时候这两个参数分别是 7 和 9,那么现在 eax 和 edx 寄存器内就是 7 和 9 了。下一步,执行 add eax, edx,这条指令是做加法的意思,其具体含义是 eax = eax + edx,那么也就是说将 edx 寄存器内的值加到 eax 上,所以现在 eax 就是这个加法函数的结果了,正好 eax 寄存器是返回值寄存器,所以下面就直接 return 了(先不管 pop 指令)

补充:

相对应 add 的,还有 sub(减法),mul(乘法),divl(除法),sall(左移),salr(右移),neg(取补),not(取反)等基础计算指令

接下来就了解以下什么是栈

从搜出来的简介可以知道对于每一个程序,其启动的时候,内核会为其分配一段内存,称为栈。

在上面这个add程序中,假设启动后内核为其分配的栈空间为 0xff00 - 0x10000,那么在启动的时候,rsp 寄存器就会被赋值为 0x10000,也就是栈顶的位置

补充:

rsp 寄存器储存的总是当前栈顶的位置

接下来,在 main 函数启动的时候,会执行 push rbp 指令,这个指令就相当于下面这两条指令

sub rsp, 8
mov QWORD PTR [rsp], rbp

首先 sub 指令将栈顶向下移了 8 个字节,也就是对 rsp 减个 8,然后将 rbp 寄存器内的值复制到 rsp 所指的地址上,前面的 QWORD PTR 表明我们要复制 8 个字节,也就是说将 rbp 寄存器内的 8 个字节(64 位)复制到了我们刚刚“开辟”出来的 8 字节在栈上的空间。

补充:

QWORD(8 字节)、 DWORD(4 字节)、WORD(2 字节)、BYTE(1 字节)

前面说过,rsp 总是指向栈顶的位置,假设在进入 main 函数的时候(main 并不是真正的程序入口),rsp 寄存器指向 0xff80 的位置,那么执行了 push rbp 以后,栈就变成了这个样子

到这里push 的含义其实就很明确了,就是将一个值给压到栈里面去(栈顶地址更高),在 main 函数中,push rbp 的作用其实是将 rbp 寄存器的值临时储存到栈里面,这样就可以拿 rbp 寄存器去干别的事了,只需要在返回之前将 rbp 寄存器的值还回去就好了

那么现在就知道上面那两个东西是什么了——栈,先将传进来的两个参数作为临时变量储存在了栈中

现在搞懂了那两个东西是栈,那add函数的完整过程就是由 push rbp 将 rbp 原本的值保存在栈中,然后 mov rbp, rsp ,rsp移动到rbp的位置,rsp 指向的是刚刚被压入栈中的 rbp 值,使用 rbp 寄存器来储存当前栈顶的位置(这个位置是栈帧的基地址,用于访问栈帧中的局部变量、函数参数和返回地址),再将传入的两个参数(esi,edi)保存到栈中, -4[rbp] 指的是 rbp 所指的地址减 4 后的地址,同理 -8[rbp] 就是 rbp 所指的地址减 8 后的地址(这些偏移量指定了栈帧中的位置),因为这两个参数都是 int,都是 4 字节(整数参数通常是四字节大小),所以对于每个参数就只需要给 4 个字节的栈空间即可,再然后,将这两个值复制到了 edx 和 eax 寄存器中,并完成加法,在返回前还需要 pop rbp ,pop 和 push 是对应的,push 是压栈,pop 就是出栈, pop rbp 就是将 rbp 原本的值还给 rbp 寄存器,这样可以保证在这个函数调用的过程中原本的环境(即一些变量等)没有发生改变,最后再通过 ret 指令返回,返回到main函数中 call add 指令的下一条指令,对于调用 add 函数的 main 函数而言,它也拿到了它想要的 add 的结果,储存在 eax 寄存器中,他只需要从这个寄存器内拿结果就好了

2.32位传参补充

在 32 位的 Linux 程序下,gcc 并不会默认使用寄存器来传递参数,而是会使用栈,第一个参数就第一个 push 到栈中

例如

int add(int a, int b){
    return a + b;
}

但是在32位汇编中call add是下面这个样子

push 1
push 2
call add

等价于 add(1, 2)

3.逻辑控制

这里在前面的过程中就好奇 ret 是依靠什么记住返回地址在哪的?它怎么知道要返回到 call add 的下一条指令?

在这之前,需要对 JMP 指令和 CMP/TEST 指令有个基本了解

CMP:CMP 表示比较两个寄存器或者内存中的值,比较的结果会影响到标志寄存器

标志位寄存器:标志位寄存器是一个 64 位的寄存器,其内部有很多标志位,什么是标志位?这里先把 64 位的寄存器看成 64 个二进制位,然后,先考虑只用其中的 3 个位,其中第一位表示a,第二位表示b,第三位表示c,那么如果我今天什么都没干,就可以用 000 表示我今天的状态,而如果我今天只有c,就可以用 001 表示我今天的状态,这样就可以用这三个位来表示我今天做了什么,而这三个位就是标志位,而标志位寄存器就是用来储存这些标志位的,CMP 指令就是用来改变标志位的,比如说,如果两个值相等,那么 ZF(零标志位)就会被置为 1,如果两个值不相等,那么 ZF 就会被置为 0,这个 ZF 就是一个标志位,用来标志两个值是否相等

跳转:有了标志位,就可以根据标志位来决定是否跳转了,假设要求是如果相等的话就跳转,那么可以这么写

je 0x12345678

其中 je 表示 JUMP IF EQUAL,即相等就跳转,其等价于 JUMP IF ZF = 1,即如果 ZF 标志位为 1,就跳转到 0x12345678 这个地址,而这个地址就是我们要跳转到的地址,这个地址可以是一个函数的地址,也可以是一个标签的地址

补充:

函数地址:函数的地址是指函数在内存中的起始位置,这是一个内存地址,指向函数的第一条指令。当你调用一个函数时,你实际上是在告诉程序跳转到这个内存地址去执行函数的代码。在汇编语言中,函数的地址可以通过函数的名称来引用,前提是该名称已经被正确地链接到其内存地址。

标签地址:标签的地址就是它所标记位置的内存地址。在汇编程序中,你可以使用标签来跳转到代码中的特定位置,或者作为数据的偏移量。

这里说一下 CMP 指令具体是怎么比较两个数的:

CMP eax, ebx 等价于 SUB eax, ebx,即 eax - ebx,但是不会将结果放回 eax,并同时会影响标志位,如果说现在减完的结果为 0,那么 ZF 就会被置为 1,如果不为 0,那么 ZF 就会被置为 0

接下来就可以写一个if语句来看看了

#include<stdio.h>

int main(){
    int a = 1;
    int b = 2;
    if(a == b){
        printf("a == b");
    }
    return 0;
}

用gcc编译

删除掉用不到的,就是下面这个样子

.LC0:
    .string "a == b"
main:
    push    rbp
    mov rbp, rsp
    mov DWORD PTR -4[rbp], 1
    mov DWORD PTR -8[rbp], 2
    mov eax, DWORD PTR -4[rbp]
    cmp eax, DWORD PTR -8[rbp]
    jne .L2
    lea rdi, .LC0[rip]
    mov eax, 0
    call    printf@PLT
.L2:
    mov eax, 0
    pop rbp
    ret

可以看到,实现的原理其实就是,先将 a 和 b 的值分别复制到 eax 和 edx 寄存器中,然后比较 eax 和 edx 寄存器中的值,如果相等就跳转到 .L2 这个标签所在的位置,如果不相等就继续往下执行

这个表是人家列出的常用跳转指令

4.循环

有了跳转,就可以实现循环了,比如说,要实现一个让程序一直输出 hello world 的循环,那么可以这么写

#include<stdio.h>

int main(){
    while(1){
        printf("hello world");
    }
    return 0;
}

然后用gcc编译并打开

删掉没用的就是下面这个样子

.LC0:
    .string "hello world"
main:
    push    rbp
    mov rbp, rsp
.L2:
    lea rdi, .LC0[rip]
    mov eax, 0
    call    printf@PLT
    jmp .L2
    mov eax, 0
    pop rbp
    ret

本质上就是 JMP 指令的使用,而像 for 循环,本质上就是 CMP 套 JMP,仍然是一样

#include<stdio.h>

int main(){

    for(int i = 0; i < 10; i++){

        printf("hello world");

    }

    return 0;

}

依旧用gcc编译并打开

删掉多余的

.LC0:
    .string "hello world"
main:
    push    rbp
    mov rbp, rsp 
    mov DWORD PTR -4[rbp], 0 // int i = 0
.L2:
    cmp DWORD PTR -4[rbp], 9 // i < 10
    jg  .L3                  // 不满足条件就跳转到.L3,即跳出循环
    lea rdi, .LC0[rip]
    mov eax, 0
    call    printf@PLT
    add DWORD PTR -4[rbp], 1 // i++
    jmp .L2                  // 返回到for循环的开始
.L3:
    mov eax, 0
    pop rbp
    ret

最后,是 TEST 指令,其实和 CMP 指令差不多,只不过其是等价于 AND 指令,即 TEST eax, ebx 等价于 AND eax, ebx,其会将 eax 和 ebx 寄存器内的值进行与操作,并同时会影响标志位,如果说现在与完的结果为 0,那么 ZF(零标志位)就会被置为 1,如果不为 0,那么 ZF 就会被置为 0

补充:

test 指令是一种位操作指令,它主要用于检查一个或多个特定位是否被设置(即为1)。test 指令的工作原理与 and 指令类似,但它不存储结果,只设置标志寄存器。这使得 test 指令非常适合用于条件判断,因为它可以快速检查位状态而不影响原始数据。

test 指令的执行过程如下:

  1. 将 source 的值与 destination 的值进行按位与(AND)操作。
  2. 根据结果设置标志寄存器(特别是零标志 ZF、符号标志 SF、辅助进位标志 AF 和奇偶标志 PF)。
    • 零标志 (ZF):如果 AND 操作的结果为零,则设置为1,否则为0。
    • 符号标志 (SF):如果结果的最高位(即符号位)为1,则设置为1,表示结果为负。
    • 辅助进位标志 (AF):如果 AND 操作的结果在低4位中有进位,则设置为1。
    • 奇偶标志 (PF):如果结果中的1的个数为偶数,则设置为1。

最后提一下标志位,实际上标志位是很多的,因为 SUB ADD 等操作是会产生溢出的,以及会有负数处理的情况,比如说 2222-3333=-1111,这是导致了正数被减为了负数,这种情况就会影响标志位,比如说,如果是正数减为了负数,那么 SF(符号标志位)就会被置为 1,如果是负数减为了正数,那么 SF 就会被置为 0,而 OF(溢出标志位)就会被置为 1,如果没有溢出,那么 OF 就会被置为 0

5.函数调用

在上面已经认识到了函数调用的过程,但是返回具体是怎么返回的呢?其实只需要拆解 call 指令和 ret 指令即可,先看 call

push rip
jmp func

rip 寄存器是受到硬件控制,永远指向下一条指令的地址,所以,先将 rip 寄存器内的值压栈,然后跳转到 func 函数,这样,func 函数就可以把要返回的地址储存在栈里

pop rip

将栈顶的值弹出到 rip 寄存器中,这样就可以返回到 call 指令的下一条指令了

其实函数调用的过程就是将返回地址压栈,然后跳转到函数,然后函数执行完毕后,再将返回地址弹出到 rip 寄存器中,这样就可以返回到 call 指令的下一条指令

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

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

相关文章

“乡村探索者”:村旅游网站的移动应用开发

3.1 可行性分析 从三个不同的角度来分析&#xff0c;确保开发成功的前提是有可行性分析&#xff0c;只有进行提前分析&#xff0c;符合程序开发流程才不至于开发过程的中断。 3.1.1 技术可行性 在技术实现层次&#xff0c;分析了好几种技术实现方法&#xff0c;并且都有对应的成…

Python + 深度学习从 0 到 1(02 / 99)

希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【深度学习从 0 到 1】谢谢你的支持&#xff01; ⭐ Keras 快速入门&#xff1a; 神经网络的基本数据结…

MySQL用户授权

什么是数据库 数据库概述&#xff1a;数据库是按照一定的数据结构将数据存储在存储器的集合常见数据库软件 软件名开源跨平台厂 商Oracle否是甲骨文MySQL是是甲骨文SQL Server否否微软DB2否是IBMMongoDB是是MongoDB Inc.Redis是是开源软件Memcached是是开源软件 DB (DataBas…

2025年我国网络安全发展形势展望

展望2025年&#xff0c;我国网络安全产业有望迎来新的快速增长阶段&#xff0c;零信任安全架构将在各行各业加快应用落地&#xff0c;数据安全技术攻关和应用进程加快&#xff0c;关键基础设施安全能力不断提升。同时&#xff0c;也应关注国家级网络对抗风险加剧、网络安全产业…

GitPuk安装配置指南

GitPuk是一款开源免费的代码管理工具&#xff0c;上篇文章已经介绍了Gitpuk的功能与优势&#xff0c;这篇文章将为大家讲解如何快速安装和配置GitPuk&#xff0c;助力你快速的启动GitPuk管理代码 1. 安装 支持 Windows、Mac、Linux、docker 等操作系统。 1.1 Windows安装 下载…

ArcGIS+MIKE21 洪水淹没分析、溃坝分析,洪水淹没动态效果

洪水淹没分析过程&#xff1a; 一、所需数据&#xff1a; 1.分析区域DEM数据 二、ArcGIS软件 1.提取分析区域DEM&#xff08;水库坝下区域&#xff09; 2.DEM栅格转点 3.计算转换后几何点的x和y坐标值&#xff08;精度20、小数位3&#xff09; 4.导出属性表&#xff0c;形式…

中伟视界:AI识别摄像头+AI预警平台在矿山皮带空载监测中的应用

在矿山开采和矿物处理过程中&#xff0c;皮带运输机扮演着举足轻重的角色。它们负责将矿石、煤炭等物料从一处运送到另一处&#xff0c;是矿山生产流程中不可或缺的一环。然而&#xff0c;皮带运输机在运行过程中也面临着一些挑战&#xff0c;其中之一便是皮带空载问题。皮带空…

探索多模态大语言模型(MLLMs)的推理能力

探索多模态大语言模型&#xff08;MLLMs&#xff09;的推理能力 Multimodal Large Language Models (MLLMs) flyfish 原文&#xff1a;Exploring the Reasoning Abilities of Multimodal Large Language Models (MLLMs): A Comprehensive Survey on Emerging Trends in Mult…

AIGC时代:如何快速搞定Spring Boot+Vue全栈开发

文章目录 一、Spring Boot基础二、Vue.js基础三、Spring Boot与Vue.js集成四、性能优化与最佳实践《快速搞定Spring BootVue全栈开发》内容简介作者简介目录前言/序言本书内容本书特点读者对象 随着人工智能生成内容&#xff08;AIGC&#xff09;技术的迅速发展&#xff0c;内容…

mongodb和Cassandra

mongodb的一致性问题&#xff1a; 15.MongoDB的一致性(读关注与写关注)_mongo w选项-CSDN博客 孤儿节点问题&#xff1a; 技术干货 | MongoDB 偶遇孤儿文档及处理方法-腾讯云开发者社区-腾讯云 分片集群MongoDB迁移前清除孤儿文档 由数据迁移至MongoDB导致的数据不一致问题…

nginx-rtmp服务器搭建

音视频服务器搭建 本文采用 nginx/1.18.0和nginx-rtmp-module模块源代码搭建RTMP流媒体服务器 流程 查看当前服务器的nginx版本下载nginx和nginx-rtmp-module源代码重新编译nginx&#xff0c;并进行相关配置&#xff08;nginx.conf、防火墙等&#xff09;客户端测试连接测试搭…

初始 ShellJS:一个 Node.js 命令行工具集合

一. 前言 Node.js 丰富的生态能赋予我们更强的能力&#xff0c;对于前端工程师来说&#xff0c;使用 Node.js 来编写复杂的 npm script 具有明显的 2 个优势&#xff1a;首先&#xff0c;编写简单的工具脚本对前端工程师来说额外的学习成本很低甚至可以忽略不计&#xff0c;其…

(echarts)数据地图散点类型根据条件设置不同的标记图片

(echarts)数据地图散点类型根据条件设置不同的标记图片 1.用在线工具将本地图片转化base64格式 data(){return { base64Img:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQ...",} }在线转换地址&#xff1a;https://www.jyshare.com/front-end/59/ 2.symbol属…

Linux高级--2.4.5 靠协议头保证传输的 MAC/IP/TCP/UDP---协议帧格式

任何网络协议&#xff0c;都必须要用包头里面设置写特殊字段来标识自己&#xff0c;传输越复杂&#xff0c;越稳定&#xff0c;越高性能的协议&#xff0c;包头越复杂。我们理解这些包头中每个字段的作用要站在它们解决什么问题的角度来理解。因为没人愿意让包头那么复杂。 本…

网络下载ts流媒体

网络下载ts流媒体 查看下载排序合并 很多视频网站&#xff0c;尤其是微信小程序中的长视频无法获取到准确视频地址&#xff0c;只能抓取到.ts片段地址&#xff0c;下载后发现基本都是5~8秒时长。 例如&#xff1a; 我们需要将以上地址片段全部下载后排序后再合成新的长视频。 …

小程序租赁系统开发指南与实现策略

内容概要 在如今这个快节奏的时代&#xff0c;小程序租赁系统的开发正逐渐成为许多商家提升服务质量与效率的重要选择。在设计这样一个系统时&#xff0c;首先要明白它的核心目标&#xff1a;便捷、安全。用户希望在最短的时间内找到需要的物品&#xff0c;而商家则希望通过这…

机器人C++开源库The Robotics Library (RL)使用手册(一)

强大的、完整的C机器人开源库 1、是否可以免费商用&#xff1f;2、支持什么平台&#xff1f;3、下载地址4、开始&#xff01; 1、是否可以免费商用&#xff1f; Robotics Library&#xff08;RL&#xff09;是一个独立的C库&#xff0c;用于机器人运动学、运动规划和控制。它涵…

Android unitTest 单元测试用例编写(初始)

文章目录 了解测试相关库导入依赖库新建测试文件示例执行查看结果网页结果其他 本片讲解的重点是unitTest&#xff0c;而不是androidTest哦 了解测试相关库 androidx.compose.ui:ui-test-junit4: 用于Compose UI的JUnit 4测试库。 它提供了测试Compose UI组件的工具和API。 and…

Ngnix介绍、安装、实战及用法!!!

一、Nginx简介 1、Nginx概述 Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理服务器&#xff0c;特点是占有内存少&#xff0c;并发能力强&#xff0c;能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数 。 2、正向代理 正向代理&#xff1a;如果把局…

国内智能编程助手简易对比:文心一言、通义千问、智谱AI、讯飞星火、Kimi

最近想给IDE选择一个智能编程助手插件&#xff0c;但鉴于国内百花齐放的现状&#xff0c;一时也不好选择用哪个。挑了几个主流的的平台&#xff0c;分别输入“用python实现雪花纷飞的场景”的简单需求&#xff0c;看看效果对比。备注&#xff1a;因国外插件使用成本远高于国内的…