五、c++学习(加餐1:汇编基础学习)

经过前面几节课的学习,我们在一些地方都会使用汇编来分析,我们学习汇编,只是学习一些基础,主要是在我们需要深入分析语法的时候,使用汇编分析,这样会让我们更熟悉c++编译器和语法。

从这节课开始,会持续加餐,争取把类对象模型,分析出来。好了,不多说了,我们继续。

C++学习,b站直播视频

文章目录

    • 5.1 x86汇编介绍
      • 5.1.1 Intel汇编
      • 5.1.2 AT&T汇编
    • 5.2 x86寄存器介绍
      • 5.2.1 Intel寄存器解读
      • 5.2.2 Intel对数据的描述
      • 5.2.3 寄存器作用描述
    • 5.3 x86常用指令
      • 5.3.1 mov 传送指令
      • 5.3.2 push&pop
      • 5.3.3 lea取地址
      • 5.3.4 跳转语句
      • 5.3.5 函数调用&返回
      • 5.3.6 算术运算
      • 5.3.7 位运算指令
      • 5.3.8 附加:rep stos
    • 5.4 AT&T汇编分析
    • 5.5 Intel vs AT&T
      • 5.5.1 操作数长度
      • 5.5.2 寄存器命名
      • 5.5.3 立即数
      • 5.5.4 操作数的方向
      • 5.5.5 内存单元操作数
      • 5.5.6 间接寻址方式
      • 5.5.7 总结
    • 5.6 函数调用详解
      • 5.6.1 函数参数约定
        • 5.6.1.1 Intel方式
        • 5.6.1.2 AT&T方式
      • 5.6.2 函数的栈帧
        • 5.6.2.1 Intel方式
        • 5.6.2.2 AT&T方式
      • 5.6.3 函数返回值
        • 5.6.3.1 Intel方式
        • 5.6.3.2 AT&T方式
      • 5.6.4 函数退出
        • 5.6.4.1 Intel方式
        • 5.6.4.2 AT&T方式
    • 5.7 扩展:ARM平台汇编分析
      • 5.7.1 RISC与CISC的区别
      • 5.7.2 ARM寄存器解读
      • 5.7.3 ARM对数据的描述
      • 5.7.4 ARM汇编浅析

5.1 x86汇编介绍

5.1.1 Intel汇编

Windows派系的汇编代码,VS就是我们一直用的这个IDE,就是这种风格的。不知道clang是什么风格,clang占的内存较大,我就不安装了,有条件的同学,可以自行测试。

后面几节课都是使用vs2022来分析的,所以这种汇编风格,我们是需要学习的。可以先来看看:

int func(int i)
{
00007FF72DF617E0 89 4C 24 08          mov         dword ptr [rsp+8],ecx  
00007FF72DF617E4 55                   push        rbp  
00007FF72DF617E5 57                   push        rdi  
00007FF72DF617E6 48 81 EC E8 00 00 00 sub         rsp,0E8h  
00007FF72DF617ED 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF72DF617F2 48 8D 0D 6E F8 00 00 lea         rcx,[00007FF72DF71067h]  
00007FF72DF617F9 E8 54 FB FF FF       call        00007FF72DF61352  
	return 2 * i;
00007FF72DF617FE 8B 85 E0 00 00 00    mov         eax,dword ptr [rbp+00000000000000E0h]  
00007FF72DF61804 D1 E0                shl         eax,1  
}
00007FF72DF61806 48 8D A5 C8 00 00 00 lea         rsp,[rbp+00000000000000C8h]  
00007FF72DF6180D 5F                   pop         rdi  
00007FF72DF6180E 5D                   pop         rbp  
00007FF72DF6180F C3                   ret  

int main()
{
00007FF72DF61830 40 55                push        rbp  
00007FF72DF61832 57                   push        rdi  
00007FF72DF61833 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF72DF6183A 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF72DF6183F 48 8D 0D 21 F8 00 00 lea         rcx,[00007FF72DF71067h]  
00007FF72DF61846 E8 07 FB FF FF       call        00007FF72DF61352  
	// std::cout << "Hello World!\n";

	int s = func(255);
00007FF72DF6184B B9 FF 00 00 00       mov         ecx,0FFh  
00007FF72DF61850 E8 82 F8 FF FF       call        00007FF72DF610D7  
00007FF72DF61855 89 45 04             mov         dword ptr [rbp+4],eax  
	return 0;
00007FF72DF61858 33 C0                xor         eax,eax  
}
00007FF72DF6185A 48 8D A5 E8 00 00 00 lea         rsp,[rbp+00000000000000E8h]  
00007FF72DF61861 5F                   pop         rdi  
00007FF72DF61862 5D                   pop         rbp  
00007FF72DF61863 C3                   ret  

5.1.2 AT&T汇编

AT&T汇编:读作“AT and T”,是 American Telephone & Telegraph 的缩写,是在贝尔实验室多年运营。

主要使用的地方就是GCC,Objdump。(这个我们学linux就知道了)。

主要使用的平台:Linux、Unix、Max OS、iOS(模拟器)

同一份代码,我们在linux下看看:

0000000000401146 <_Z4funci>:
  401146:	55                   	push   %rbp
  401147:	48 89 e5             	mov    %rsp,%rbp
  40114a:	89 7d fc             	mov    %edi,-0x4(%rbp)
  40114d:	8b 45 fc             	mov    -0x4(%rbp),%eax
  401150:	01 c0                	add    %eax,%eax
  401152:	5d                   	pop    %rbp
  401153:	c3                   	retq   

0000000000401154 <main>:
  401154:	55                   	push   %rbp
  401155:	48 89 e5             	mov    %rsp,%rbp
  401158:	48 83 ec 10          	sub    $0x10,%rsp
  40115c:	bf ff 00 00 00       	mov    $0xff,%edi
  401161:	e8 e0 ff ff ff       	callq  401146 <_Z4funci>
  401166:	89 45 fc             	mov    %eax,-0x4(%rbp)
  401169:	b8 00 00 00 00       	mov    $0x0,%eax
  40116e:	c9                   	leaveq 
  40116f:	c3                   	retq 

感觉还是g++的风格简单些。

5.2 x86寄存器介绍

在这里插入图片描述

这个图来自《深入理解计算机系统》。

这些寄存器都是x86体系下的寄存器,当然x86的寄存器还有很多,我下载过intel的芯片手册,都是英文,看的头都大了。我们只要了解这些就可以了。当然x86还有存浮点数的寄存器,这个没有了解,就不说,上面这个图,才是我们重点。注意最后面注释介绍的是AT&T。

5.2.1 Intel寄存器解读

Intel的寄存器有一个很有意思的地方,就是因为要兼容以前的,所以寄存器的位数从8位到16位到32位再到64位都有,我们看上面第一个寄存器RAX:

64位叫:rax

32位叫:eax

16位叫:ax

8位叫:al

所以我们待会看汇编的时候,就会看到,不过一般会看到rax和eax,现在16位和8位已经被淘汰了。

5.2.2 Intel对数据的描述

Intel作为业界的老大哥,背负的历史包袱过重,哈哈哈。

c声明Intel数据类型
char字节
short
int双字
long四字

总感觉当年16位的时候,觉得够大了吧,所以叫字,然后int出来之后,懵逼了,只能叫双字,这个其实不用记,只是简单介绍一下。(到时候可以看看ARM,没有历史负担就是任性)

5.2.3 寄存器作用描述

为啥是简单描述,是因为两种汇编风格对函数调用的传参不一样,这就有点难搞,哈哈哈。

在这里插入图片描述

图片引用了https://blog.csdn.net/c2682736/article/details/122349851这个链接的。

特殊用处寄存器介绍:

rax:函数返回值

rdx:在Intel方式中做第二个参数,在AT&T中做第三个参数,甚至还可以做第二个返回值寄存器。

rsp:栈顶指针,linux是满减栈,看着windows是往上加的栈

rbp:栈底指针,两个指针一减,就可以算出栈大小。

rip:指令寄存器

函数参数寄存器:这个因为两种方式不一样,所以在后面再详解。

5.3 x86常用指令

上面介绍了寄存器,接着我们介绍一下常用指令,指令+寄存器的操作,就相当于我们的一条语句。不过这个指令,不需要记,也记不住,说实话,看到不懂再查询。

5.3.1 mov 传送指令

// 我们先开始看mov命令,这个命令是出常见的,也是我们c++使用的赋值语句。
     int s = 1;
00007FF72F8E562D C7 45 04 01 00 00 00 mov         dword ptr [rbp+4],1  
    // rbp是栈基地址,rbp+4就是s的地址,mov就是把1赋值到[rbp+4] (dword就是双字)
    // []是对这个地址寻址,也就是*(rbp+4)的意思
	int s1 = s;
00007FF72F8E5634 8B 45 04             mov         eax,dword ptr [rbp+4]  
    // 这是两个变量赋值,两个变量赋值,需要借助寄存器了,eax.
    // 第一次,先把[rbp+4]赋值eax寄存器
00007FF72F8E5637 89 45 24             mov         dword ptr [rbp+24h],eax
    // 第二次,再把这个寄存器eax赋值到[rbp+24h],[rbp+24]就是s1的地址
	int* ps = &s;
00007FF72F8E563A 48 8D 45 04          lea         rax,[rbp+4]  
    // lea是我们后面介绍的,取地址指令,把[rbp+4]的地址赋值到rax中
00007FF72F8E563E 48 89 45 48          mov         qword ptr [rbp+48h],rax 
    // 然后在把rax的值赋值到[rbp+48h]中,(qword是四字,指针在64位系统都是8字节)
	*ps = 111;
00007FF72F8E5642 48 8B 45 48          mov         rax,qword ptr [rbp+48h]  
    // 把ps的值赋值到rax(因为ps是指针,所以这个值其实就是地址)
00007FF72F8E5646 C7 00 6F 00 00 00    mov         dword ptr [rax],6Fh 
    // 然后对rax就是指向的内存就行赋值,[]就是寻址的意思

5.3.2 push&pop

这两个命令一起来,很明显push是压栈,pop是出栈,只有调用函数才有这个操作。

我们来看看:

int test()
{
00007FF7486817D0 40 55                push        rbp  
    // 这是把上一个函数的栈基地址保存到栈里,具体的函数栈帧我们后面分析
00007FF7486817D2 57                   push        rdi  
    // rdi没记错应该是第一个参数,我也不清楚为啥要入栈????
00007FF7486817D3 48 81 EC E8 00 00 00 sub         rsp,0E8h  
   // 这句也是我疑惑的地方,不是不知道这句意思,是不知道为啥windos栈能隔这么大。
    // 这句意思就是把栈指针往下减0E8h这么大空间
00007FF7486817DA 48 8D 6C 24 20       lea         rbp,[rsp+20h]
    // 然后在把rsp往上加20h作为这个函数的栈空间,这个我试过了,如果变量过大,这个空间也会变大,等下我们试试
00007FF7486817DF 48 8D 0D 81 F8 00 00 lea         rcx,[00007FF748691067h]  
00007FF7486817E6 E8 67 FB FF FF       call        00007FF748681352  
    // 后面这两句我更疑惑了,调用了一个函数,就不分析这个函数干啥了。
	return 0;
00007FF7486817EB 33 C0                xor         eax,eax  
    // xor是后面的指令,意思就是异或,反正两个都是0
}
00007FF7486817ED 48 8D A5 C8 00 00 00 lea         rsp,[rbp+00000000000000C8h]  
    // rbp+C8h其实就是等于rsp原来指向的地址,因为上面rbp=rsp+20h,这个20h+C8h=E8h,就是当初rsp减少的空间
00007FF7486817F4 5F                   pop         rdi  
    // 然后再把rdi从栈中弹出到rdi寄存器里
00007FF7486817F5 5D                   pop         rbp  
    // 上面把rsp还原了,这里肯定要把rbp也还原
00007FF7486817F6 C3                   ret 

5.3.3 lea取地址

这个上面说了,就不继续说,用处就是取地址。

5.3.4 跳转语句

	int s2 = 0;
00007FF650C34671 C7 45 64 00 00 00 00 mov         dword ptr [rbp+64h],0  
	if (s == 1)
00007FF650C34678 83 7D 04 01          cmp         dword ptr [rbp+4],1  
        // cmp就是比较语句,比较这两个值是否想等
        // 如果operand1等于operand2,则ZF被置为1;否则,ZF被置为0。如果operand1小于operand2,则SF被置为1;否则,SF被置为0。如果操作过程中有进位,则CF被置为1;否则,CF被置为0。
00007FF650C3467C 75 07                jne         00007FF650C34685  
        // jne是判断上一条语句不等于0(即ZF标志位为0),两个不想等ZF为0才跳转
        // 还有其他跳转命令je jle  jl jg等等
	{
		s2 = 1;
00007FF650C3467E C7 45 64 01 00 00 00 mov         dword ptr [rbp+64h],1  
	}

	s2 = 2;
00007FF650C34685 C7 45 64 02 00 00 00 mov         dword ptr [rbp+64h],2 
    // 后面就不解释了

5.3.5 函数调用&返回

	// 测试push pop
	test();
00007FF650C3466C E8 54 CD FF FF       call        00007FF650C313C5 

call 函数调用&ret 返回这也比较常见了。

5.3.6 算术运算

  • add 想加
  • sub 相减
  • inc 加1
  • mul 相乘
  • div 相除
	int s4 = s3 + 4;
00007FF63E6F4C66 8B 85 84 00 00 00    mov         eax,dword ptr [rbp+0000000000000084h]  
00007FF63E6F4C6C 83 C0 04             add         eax,4  

其他的就不演示了。

5.3.7 位运算指令

  • and 与运算
  • or 或运算
  • xor 异或运算
  • not 取反

不演示了。

5.3.8 附加:rep stos

	int s5[20] = { 0 };
00007FF6BD944C75 48 8D 85 D0 00 00 00 lea         rax,[rbp+00000000000000D0h] 
    // 把数组的地址赋值到rax中
00007FF6BD944C7C 48 8B F8             mov         rdi,rax  
    // 然后在赋值到rdi中
00007FF6BD944C7F 33 C0                xor         eax,eax  
    // 然后把eax清0
00007FF6BD944C81 B9 50 00 00 00       mov         ecx,50h  
    // 50h是这个数组的大小
00007FF6BD944C86 F3 AA                rep stos    byte ptr [rdi] 

这个还挺常用的,用在一个结构体或类或数组,一些符合数据结构置0的时候。

后面的rep stos是我们重点,这其实是两个指令复合使用。

REP(Repeat)汇编指令是一种重复执行指令的控制指令,它通常和其他指令组合使用,用于在处理字符串或数组时进行重复操作。

REP指令可以和多个其他指令搭配使用,如MOV、STOS等。其语法如下:

rep instruction

其中,instruction是要重复执行的指令。REP指令会将ECX寄存器中的值作为计数器,循环执行instruction指定的操作。每次循环都会将ECX减1,直到ECX的值为0为止。

还记得刚刚ecx赋值了50h

STOS指令:将AL/AX/EAX的值存储到[EDI]指定的内存单元

STOS指令使用AL(字节 - STOSB),AX(字 - STOSW)或EAX(对于双 - STOSD的)数据复制目标字符串,在内存中通过ES:DI指向。

我们这个数组是int,是双子,所以使用eax,我们前面不是刚用eax异或就是为了清0.

所以这个句话就是把数组清0了。

5.4 AT&T汇编分析

上面我们是对windos下的Intel汇编格式的分析,下面我们就分析linux下的AT&T汇编格式,这里就不在区分了,我们直接看了,看完了,我们下一节,就可以总结两种方式的差异。

// 编译是需要加-g,objdump -S 才能反汇编出带源码   
0000000000401146 <_Z4testv>:

#include <iostream>


int test()
{
  401146:	55                   	push   %rbp
  401147:	48 89 e5             	mov    %rsp,%rbp
	return 0;
  40114a:	b8 00 00 00 00       	mov    $0x0,%eax
}
  40114f:	5d                   	pop    %rbp
  401150:	c3                   	retq   
      // retq就是退出
      // linux的函数风格我比较喜欢,因为比较简单,不像windos那么复杂

0000000000401151 <main>:

int main()
{
  401151:	55                   	push   %rbp
      // 把栈基地址入栈,寄存器是用%
  401152:	48 89 e5             	mov    %rsp,%rbp
      // 把新的rsp地址移动到rbp,AT&T的mov源操作数是在中间
  401155:	48 83 ec 70          	sub    $0x70,%rsp
       // 把rsp栈的大小设置为0x70h,这么大
	int s = 1;
  401159:	c7 45 e0 01 00 00 00 	movl   $0x1,-0x20(%rbp)
      // l其实就是int,rbp-20h就是s的地址,原来这就是满减栈的意思,window是栈是往上加
	int s1 = s;
  401160:	8b 45 e0             	mov    -0x20(%rbp),%eax
      // 如果不看源码,这个确实不好理解,就是把rbp-20h的值赋值给eax
  401163:	89 45 fc             	mov    %eax,-0x4(%rbp)
      // 然后再把eax的值赋值到s1,这里有点奇怪为啥s1的地址是rbp-4h??
	int* ps = &s;
  401166:	48 8d 45 e0          	lea    -0x20(%rbp),%rax
      // lea取地址,熟悉不,就是把s的地址赋值到rax中
  40116a:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
      // 然后再在rax赋值到ps里,这里ps的地址是rbp-10h
	*ps = 111;
  40116e:	48 8b 45 f0          	mov    -0x10(%rbp),%rax
  401172:	c7 00 6f 00 00 00    	movl   $0x6f,(%rax)
      // 这个也是赋值,这个()也是寻址意思,跟Intel的[]是一个意思

	// 测试push pop
	test();
  401178:	e8 c9 ff ff ff       	callq  401146 <_Z4testv>
      // 调用也是用call,不过加了个后缀

	int s2 = 0;
  40117d:	c7 45 ec 00 00 00 00 	movl   $0x0,-0x14(%rbp)
	if (s == 1)
  401184:	8b 45 e0             	mov    -0x20(%rbp),%eax
  401187:	83 f8 01             	cmp    $0x1,%eax
  40118a:	75 07                	jne    401193 <main+0x42>
      // 这个cmp和jne就一样了
	{
		s2 = 1;
  40118c:	c7 45 ec 01 00 00 00 	movl   $0x1,-0x14(%rbp)
	}

	s2 = 2;
  401193:	c7 45 ec 02 00 00 00 	movl   $0x2,-0x14(%rbp)


	int s3 = 1 + 4;
  40119a:	c7 45 e8 05 00 00 00 	movl   $0x5,-0x18(%rbp)
	int s4 = s3 + 4;
  4011a1:	8b 45 e8             	mov    -0x18(%rbp),%eax
  4011a4:	83 c0 04             	add    $0x4,%eax
  4011a7:	89 45 e4             	mov    %eax,-0x1c(%rbp)
	// add也是一样的
      
	int s5[20] = { 0 };
  4011aa:	48 c7 45 90 00 00 00 	movq   $0x0,-0x70(%rbp)
  4011b1:	00 
  4011b2:	48 c7 45 98 00 00 00 	movq   $0x0,-0x68(%rbp)
  4011b9:	00 
  4011ba:	48 c7 45 a0 00 00 00 	movq   $0x0,-0x60(%rbp)
  4011c1:	00 
  4011c2:	48 c7 45 a8 00 00 00 	movq   $0x0,-0x58(%rbp)
  4011c9:	00 
  4011ca:	48 c7 45 b0 00 00 00 	movq   $0x0,-0x50(%rbp)
  4011d1:	00 
  4011d2:	48 c7 45 b8 00 00 00 	movq   $0x0,-0x48(%rbp)
  4011d9:	00 
  4011da:	48 c7 45 c0 00 00 00 	movq   $0x0,-0x40(%rbp)
  4011e1:	00 
  4011e2:	48 c7 45 c8 00 00 00 	movq   $0x0,-0x38(%rbp)
  4011e9:	00 
  4011ea:	48 c7 45 d0 00 00 00 	movq   $0x0,-0x30(%rbp)
  4011f1:	00 
  4011f2:	48 c7 45 d8 00 00 00 	movq   $0x0,-0x28(%rbp)
  4011f9:	00 
		// 看来linux下没有rep stos这个命令,在使用笨方式一个一个清0
	return 0;
  4011fa:	b8 00 00 00 00       	mov    $0x0,%eax
}
  4011ff:	c9                   	leaveq 
  401200:	c3                   	retq 

5.5 Intel vs AT&T

上面我们分析过了Intel和AT&T的汇编,然后我们来总结一下,这两种汇编的区别。

5.5.1 操作数长度

我们操作一个数的大小,其实可以通过在汇编指令上体现出来的。

AT&T的操作码后面有一个后缀,其含义就是操作码的大小。

(后缀分别为:b、w、l、q)

在Intel的语法中,则要在内存单元操作数的前面加上前缀。

(前缀分别为:byte ptr、world ptr、dworld ptr、qworld ptr)

c声明Intel数据类型Intel语法AT&T语法大小
char字节mov al,blmovb %bl,%al1
shortmov ax,bxmovw %bx,%ax2
int双字mov eax,dworld ptr[ebx]movl (%ebx),%eax4
long四字mov rax, qworld ptr[rbx]movq (%rbx),%rax8
float单精度不知道后缀s4
char *四字不知道后缀q8
double双精度不知道后缀l8

我对汇编指令操作浮点确实不熟悉,因为我们自己分析的大部分都是整数,以后如果碰到了,操作浮点的,再回来补充补充。

5.5.2 寄存器命名

通过上面的例子,应该都发现了,寄存器命名两种方式的汇编不一样。

在Intel的语法中,寄存器是不需要前缀的。

在AT&T的语法中,寄存器是需要加%前缀

Intel语法AT&T语法
mov rax, qworld ptr[rbx]movq (%rbx),%rax

5.5.3 立即数

立即数的写法也不一样。

在Intel语法中,十六进制和二进制立即数后面分别冠以h和b

在AT&T语法中,立即数前面加上$,然后如果是十六进制前面再加上0x

Intel语法AT&T语法
mov eax,8movl **$**8,%eax
mov ebx,0ffffhmovl **$**0xffff,%ebx

5.5.4 操作数的方向

这一点我就好奇,为啥搞的方向相反,这样看起代码,真的难受。

Intel与AT&T操作数的方式正好相反。

在Intel语法中,第一个操作数是目的操作数,第二个操作数是源操作数

在AT&T中,第一个数是源操作数,第二个数是目的操作数

Intel语法AT&T语法
mov eax,[ecx]movl (%ecx),%eax

5.5.5 内存单元操作数

从上面例子可以看出,内存操作数也有所不同。

在Intel的语法中,基寄存器用"[]"括起来。

在AT&T中,基寄存器用"()"括起来

Intel语法AT&T语法
mov eax,[ecx]movl (%ecx),%eax

5.5.6 间接寻址方式

与Intel的语法比较,AT&T间接寻址方式可能更晦涩难懂一些。

Intel的指令格式是:
segreg:[base+index*scale+disp]
AT&T的指令格式是:
%segreg:disp(base,index,scale)

格式解读:

  • 其中index/scale/disp/segreg 全部可选的,完全可以简化掉。
  • 如果没有指定scale而指定了index,则scale的缺省值为1.
  • segreg段寄存器依赖于指令以及应用程序是运行在实模式还是保护模式下。在实模式下它依赖于指令,在保护模式下segreg是多余的。
  • 在AT&T中,当立即数用在scale/disp中时,不应当在其前冠以$前缀
Intel语法
指令segrg:[base+index*scale+disp]
AT&T语法
指令%segreg:disp(base,index,scale)
mov eax,[ebx+10h]movl 0x10(%ebx),%eax
add eax,[ebx+ecx*2h]addl (%ebx,%ecx,0x2),%eax
lea eax,[ebx+ecx]leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h]subl -0x20(%eax,%ecx,0x4), %eax

5.5.7 总结

项目IntelAT&T说明
操作数长度mov rax, qworld ptr[rbx]movq (%rbx),%rax一个加前缀,一个加后缀
寄存器命名mov rax, qworld ptr[rbx]movq (%rbx),%raxAT&T需要加%
立即数mov ebx,0ffffhmovl **$**0xffff,%ebxAT&T需要加$
操作数的方向mov eax,[ecx]movl (%ecx),%eax刚好相反
内存单元操作数mov eax,[ecx]movl (%ecx),%eax一个使用[],一个使用()
间接寻址方式segreg:[base+index*scale+disp]%segreg:disp(base,index,scale)其实都是Intel的方式计算

5.6 函数调用详解

前面吹了这么多水,终于来到我们这一篇的重点了,趁着我们刚学习汇编,还热乎,我们来分析一下函数是怎么调用的。

5.6.1 函数参数约定

我们调用函数的时候,会进行函数传参,那函数是怎么知道我们传了几个参数,并且每个参数的值是多少??

其实这都是约定好的,要命的是不同平台约定的不一样,真是离了大谱,增加了学习成本。

5.6.1.1 Intel方式

我们用什么方法来看这个约定的方式呢?其实很简单,我们直接写10个参数,哈哈哈哈。

	test1(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
00007FF6EDCA1958 C7 44 24 48 0A 00 00 00 mov         dword ptr [rsp+48h],0Ah  
00007FF6EDCA1960 C7 44 24 40 09 00 00 00 mov         dword ptr [rsp+40h],9  
00007FF6EDCA1968 C7 44 24 38 08 00 00 00 mov         dword ptr [rsp+38h],8  
00007FF6EDCA1970 C7 44 24 30 07 00 00 00 mov         dword ptr [rsp+30h],7  
00007FF6EDCA1978 C7 44 24 28 06 00 00 00 mov         dword ptr [rsp+28h],6  
00007FF6EDCA1980 C7 44 24 20 05 00 00 00 mov         dword ptr [rsp+20h],5  // 第5个参数开始入栈了
00007FF6EDCA1988 41 B9 04 00 00 00    mov         r9d,4  // 第四个参数r9
00007FF6EDCA198E 41 B8 03 00 00 00    mov         r8d,3  // 第三个参数r8
00007FF6EDCA1994 BA 02 00 00 00       mov         edx,2  // 第二个参数rdx
00007FF6EDCA1999 B9 01 00 00 00       mov         ecx,1  // 第一个参数rcx
00007FF6EDCA199E E8 7D F9 FF FF       call        00007FF6EDCA1320 

是不是一看汇编就明白了,这里我们就分析整数,浮点不做分析,后面有兴趣可以自行分析。

  • rcx,rdx,r8,r9:用来存储整数或指针参数,安装从左到右顺序
  • xmm0,1,2,3 用来存储浮点参数
  • 其余参数会压入栈中。

5.6.1.2 AT&T方式

我们接下来看看AT&T方式:

	test1(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  40125c:	6a 0a                	pushq  $0xa       // 第10个参数
  40125e:	6a 09                	pushq  $0x9       // 第九个参数
  401260:	6a 08                	pushq  $0x8       // 第八个参数
  401262:	6a 07                	pushq  $0x7       // 第七个参数
  401264:	41 b9 06 00 00 00    	mov    $0x6,%r9d  // 第六个参数
  40126a:	41 b8 05 00 00 00    	mov    $0x5,%r8d  // 第五个参数
  401270:	b9 04 00 00 00       	mov    $0x4,%ecx  // 第四个参数
  401275:	ba 03 00 00 00       	mov    $0x3,%edx  // 第三个参数
  40127a:	be 02 00 00 00       	mov    $0x2,%esi  // 第二个参数
  40127f:	bf 01 00 00 00       	mov    $0x1,%edi  // 第一个参数
  401284:	e8 c8 fe ff ff       	callq  401151 <_Z5test1iiiiiiiiii>

这个也会看下汇编就懂了,下面是总结:

  • 当参数在 6 个以内,参数从左到右依次放入寄存器: rdi, rsi, rdx, rcx, r8, r9。(多了rdi,rsi)
  • 当参数大于 6 个, 大于六个的部分的依次从 “右向左” 压入栈中,和32位汇编一样。

5.6.2 函数的栈帧

通过上面认识到了函数参数,那我们来看看函数内部,这个函数的栈是怎么使用的。

5.6.2.1 Intel方式

int test1(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10)
{
00007FF6EDCA1810 44 89 4C 24 20       mov         dword ptr [rsp+20h],r9d  
00007FF6EDCA1815 44 89 44 24 18       mov         dword ptr [rsp+18h],r8d  
00007FF6EDCA181A 89 54 24 10          mov         dword ptr [rsp+10h],edx  
00007FF6EDCA181E 89 4C 24 08          mov         dword ptr [rsp+8],ecx  
    // 这是rsp把前面的4个参数入栈,前面4个存储在寄存器里的,需要入栈\
    // 我们来看看这里的rsp = 000000D318CFF808 rbp = 000000D318CFF860
    // 第一个参数 = 000000D318CFF808 + 8h  = 000000D318CFF810
    // 第二个参数 = 000000D318CFF808 + 10h = 000000D318CFF818
    // 第三个参数 = 000000D318CFF808 + 18h = 000000D318CFF820
    // 第10个参数 = 000000D318CFF808 + 48h = 000000D318CFF850
00007FF6EDCA1822 55                   push        rbp  
    // 
00007FF6EDCA1823 57                   push        rdi  
00007FF6EDCA1824 48 81 EC E8 00 00 00 sub         rsp,0E8h  
00007FF6EDCA182B 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF6EDCA1830 48 8D 0D 30 F8 00 00 lea         rcx,[00007FF6EDCB1067h]  
00007FF6EDCA1837 E8 25 FB FF FF       call        00007FF6EDCA1361  



	return i1 + i10;
00007FF6EDCA183C 8B 85 28 01 00 00    mov         eax,dword ptr [rbp+0000000000000128h]  
00007FF6EDCA1842 8B 8D E0 00 00 00    mov         ecx,dword ptr [rbp+00000000000000E0h]  
00007FF6EDCA1848 03 C8                add         ecx,eax  
00007FF6EDCA184A 8B C1                mov         eax,ecx  
}
00007FF6EDCA184C 48 8D A5 C8 00 00 00 lea         rsp,[rbp+00000000000000C8h]  
00007FF6EDCA1853 5F                   pop         rdi  
00007FF6EDCA1854 5D                   pop         rbp  
00007FF6EDCA1855 C3                   ret  

我把Intel的函数调用的栈帧画了一个图:

在这里插入图片描述

因为test1函数没有定义变量,所以函数栈里并没有值。如果定义了变量也会跟main函数一样,就是有点搞不懂,每个变量离的这么远是因为怕踩到内存????有知道的可以留言告诉我。

5.6.2.2 AT&T方式

0000000000401151 <_Z5test1iiiiiiiiii>:

int test1(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10)
{
  401151:	55                   	push   %rbp
  401152:	48 89 e5             	mov    %rsp,%rbp
  401155:	89 7d fc             	mov    %edi,-0x4(%rbp)
  401158:	89 75 f8             	mov    %esi,-0x8(%rbp)
  40115b:	89 55 f4             	mov    %edx,-0xc(%rbp)
  40115e:	89 4d f0             	mov    %ecx,-0x10(%rbp)
  401161:	44 89 45 ec          	mov    %r8d,-0x14(%rbp)
  401165:	44 89 4d e8          	mov    %r9d,-0x18(%rbp)
      // 这个会把其他参数都存进栈里
      



	return i1 + i10;
  401169:	8b 55 fc             	mov    -0x4(%rbp),%edx
  40116c:	8b 45 28             	mov    0x28(%rbp),%eax
  40116f:	01 d0                	add    %edx,%eax
}
  401171:	5d                   	pop    %rbp
  401172:	c3                   	retq

linux就比较节省内存,因为test1没有使用临时变量,所以栈大小是0。

在这里插入图片描述

5.6.3 函数返回值

我们都知道,函数可以返回一个值,那函数是怎么返回这个值的呢?我们继续分析

5.6.3.1 Intel方式

char test2()
{
00007FF7DFC51870 40 55                push        rbp  
00007FF7DFC51872 57                   push        rdi  
00007FF7DFC51873 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7DFC5187A 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7DFC5187F 48 8D 0D E1 F7 00 00 lea         rcx,[00007FF7DFC61067h]  
00007FF7DFC51886 E8 D6 FA FF FF       call        00007FF7DFC51361  
	char s = 9;
00007FF7DFC5188B C6 45 04 09          mov         byte ptr [rbp+4],9  
	return s;
00007FF7DFC5188F 0F B6 45 04          movzx       eax,byte ptr [rbp+4]  
    // 返回到char型
}
    
short test3()
{
00007FF7DFC519E0 40 55                push        rbp  
00007FF7DFC519E2 57                   push        rdi  
00007FF7DFC519E3 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7DFC519EA 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7DFC519EF 48 8D 0D 71 F6 00 00 lea         rcx,[00007FF7DFC61067h]  
00007FF7DFC519F6 E8 66 F9 FF FF       call        00007FF7DFC51361  
	short s = 9;
00007FF7DFC519FB B8 09 00 00 00       mov         eax,9  
00007FF7DFC51A00 66 89 45 04          mov         word ptr [rbp+4],ax  
	return s;
00007FF7DFC51A04 0F B7 45 04          movzx       eax,word ptr [rbp+4]  
}

int test4()
{
00007FF7DFC518B0 40 55                push        rbp  
00007FF7DFC518B2 57                   push        rdi  
00007FF7DFC518B3 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7DFC518BA 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7DFC518BF 48 8D 0D A1 F7 00 00 lea         rcx,[00007FF7DFC61067h]  
00007FF7DFC518C6 E8 96 FA FF FF       call        00007FF7DFC51361  
	int s = 9;
00007FF7DFC518CB C7 45 04 09 00 00 00 mov         dword ptr [rbp+4],9  
	return s;
00007FF7DFC518D2 8B 45 04             mov         eax,dword ptr [rbp+4]  
}

long long test5()
{
00007FF7873C18E0 40 55                push        rbp  
00007FF7873C18E2 57                   push        rdi  
00007FF7873C18E3 48 81 EC 08 01 00 00 sub         rsp,108h  
00007FF7873C18EA 48 8D 6C 24 20       lea         rbp,[rsp+20h]  
00007FF7873C18EF 48 8D 0D 71 F7 00 00 lea         rcx,[00007FF7873D1067h]  
00007FF7873C18F6 E8 66 FA FF FF       call        00007FF7873C1361  
	long long s = 9;
00007FF7873C18FB 48 C7 45 08 09 00 00 00 mov         qword ptr [rbp+8],9  
	return s;
00007FF7873C1903 48 8B 45 08          mov         rax,qword ptr [rbp+8]  
}

实验证明,现在是64位系统了,没有数据类型比64位的长了,尴尬,所以测不了超过rax的时候,会不会用其他寄存器了。

5.6.3.2 AT&T方式

看下linux的返回值应该是一样的。

0000000000401173 <_Z5test2v>:

char test2()
{
  401173:	55                   	push   %rbp
  401174:	48 89 e5             	mov    %rsp,%rbp
	char s = 9;
  401177:	c6 45 ff 09          	movb   $0x9,-0x1(%rbp)
	return s;
  40117b:	0f b6 45 ff          	movzbl -0x1(%rbp),%eax
}
  40117f:	5d                   	pop    %rbp
  401180:	c3                   	retq   

0000000000401181 <_Z5test3v>:

short test3()
{
  401181:	55                   	push   %rbp
  401182:	48 89 e5             	mov    %rsp,%rbp
	short s = 9;
  401185:	66 c7 45 fe 09 00    	movw   $0x9,-0x2(%rbp)
	return s;
  40118b:	0f b7 45 fe          	movzwl -0x2(%rbp),%eax
}
  40118f:	5d                   	pop    %rbp
  401190:	c3                   	retq   

0000000000401191 <_Z5test4v>:

int test4()
{
  401191:	55                   	push   %rbp
  401192:	48 89 e5             	mov    %rsp,%rbp
	int s = 9;
  401195:	c7 45 fc 09 00 00 00 	movl   $0x9,-0x4(%rbp)
	return s;
  40119c:	8b 45 fc             	mov    -0x4(%rbp),%eax
}
  40119f:	5d                   	pop    %rbp
  4011a0:	c3                   	retq   

00000000004011a1 <_Z5test5v>:

long long test5()
{
  4011a1:	55                   	push   %rbp
  4011a2:	48 89 e5             	mov    %rsp,%rbp
	long long s = 9;
  4011a5:	48 c7 45 f8 09 00 00 	movq   $0x9,-0x8(%rbp)
  4011ac:	00 
	return s;
  4011ad:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
}
  4011b1:	5d                   	pop    %rbp
  4011b2:	c3                   	retq 

看来64位系统就是这么强大,不需要两个寄存器了,直接一个寄存器就可以返回值成功了。

5.6.4 函数退出

函数退出就比较简单了。

5.6.4.1 Intel方式

00007FF7873C1907 48 8D A5 E8 00 00 00 lea         rsp,[rbp+00000000000000E8h]  
    // 处理rsp的值,这样计算就是得到前面函数的rsp
00007FF7873C190E 5F                   pop         rdi
    // 还记得rdi进栈不,现在就弹出
00007FF7873C190F 5D                   pop         rbp
    // 也继续弹出rbp
00007FF7873C1910 C3                   ret  
    // ret指令是一种汇编指令,它的作用是从一个子程序中返回到调用该子程序的主程序。在执行时,ret指令将从堆栈中弹出返回地址,并跳转到该地址继续执行主程序。(还记得下一个执行的地址,也入栈了吧)

5.6.4.2 AT&T方式

  4011b1:	5d                   	pop    %rbp
  4011b2:	c3                   	retq 

从上面写的几个简单函数分析,rsp的值都不会变,所以函数退出,只需要把rbp还原,然后在ret到下一条执行的指令。

5.7 扩展:ARM平台汇编分析

本来不打算介绍ARM,因为我们以后都是使用Intel了,ARM芯片做服务器还是比较少的,但是想想如果作为比较的方式学习还是不错的。

5.7.1 RISC与CISC的区别

RISC全称Reduced Instruction Set Compute,精简指令集计算机。

CISC全称Complex Instruction Set Computers,复杂指令集计算机。

RISC的主要特点:
1)选取使用频率较高的一些简单指令以及一些很有用但不复杂的指令,让复杂指令的功能由使用频率高的简单指令的组合来实现。

2)指令长度固定,指令格式种类少,寻址方式种类少。

3)只有取数/存数指令访问存储器,其余指令的操作都在寄存器内完成。

4)CPU中有多个通用寄存器(比CICS的多)

5)采用流水线技术(RISC一定采用流水线),大部分指令在一个时钟周期内完成。采用超标量超流水线技术,可使每条指令的平均时间小于一个时钟周期。

6)控制器采用组合逻辑控制,不用微程序控制。

7)采用优化的编译程序

CICS的主要特点:
1)指令系统复杂庞大,指令数目一般多达200~300条。

2)指令长度不固定,指令格式种类多,寻址方式种类多。

3)可以访存的指令不受限制(RISC只有取数/存数指令访问存储器)

4)各种指令执行时间相差很大,大多数指令需多个时钟周期才能完成。

5)控制器大多数采用微程序控制。

6)难以用优化编译生成高效的目标代码程序

RISC与CICS的比较
1.RISC比CICS更能提高计算机运算速度;RISC寄存器多,就可以减少访存次数,指令数和寻址方式少,因此指令译码较快。

2.RISC比CISC更便于设计,可降低成本,提高可靠性。

3.RISC能有效支持高级语言程序。

4.CICS的指令系统比较丰富,有专用指令来完成特定的功能,因此处理特殊任务效率高。
这个转载自https://blog.csdn.net/weixin_40491661/article/details/121351113

很明显,Intel的芯片就是CICS指令,现在我们来学习的ARM其实就是RISC,经常使用在手机芯片上,因为功耗较低。

5.7.2 ARM寄存器解读

在这里插入图片描述

ARM的寄存器比较多,但是指令是精简的,所以才是RISC的风格。

ARM的寄存器都是通用的都是R0-R15,但是因为ARM有7种模式,所以不同模式的寄存器用法可能不一样,主要是这些模式的寄存器虽然名字相同,但都是不同寄存器。

但是这里有两个寄存器比较特殊:

R13:栈顶指针

R15:指令寄存器

5.7.3 ARM对数据的描述

ARM的发展比较晚,基本没有经历过8位和16位的时代,所以ARM没有什么历史负担,所以对数据描述没有windows看的那么难受。

c声明Intel数据类型
char字节
short半字
int
long双字

5.7.4 ARM汇编浅析

这个就不写了,因为我现在没有交叉编译工具链,等后面有机会再说吧。

参考文章:

https://blog.csdn.net/weixin_39946798/article/details/113708680

https://blog.csdn.net/c2682736/article/details/122349851

https://blog.csdn.net/weixin_38633659/article/details/125221443

https://blog.csdn.net/oqqHuTu12345678/article/details/125676002

https://www.jianshu.com/p/338d2f85e954

https://blog.csdn.net/weixin_40491661/article/details/121351113

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

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

相关文章

SQL删除重复的记录(只保留一条)-窗口函数row_number()

文章目录 一、关于mysql表中数据重复二、聚合函数min(id)not in二、窗口函数row_number()四、补充&#xff1a;常见的窗口函数 一、关于mysql表中数据重复 关于删除mysql表中重复数据问题&#xff0c;本文中给到两种办法&#xff1a;聚合函数、窗口函数row_number()的方法。 (注…

UML类图画法及其关系

UML类图画法及其关系 本文主要是介绍 UML类图画法及其关系&#xff0c;方便今后温习&#xff01;&#xff01;&#xff01; 一、类之间的关系汇总 泛化&#xff08;Generalization&#xff09;实现&#xff08;Realization&#xff09;关联&#xff08;Association&#xff…

算法时间复杂度

参考视频&#xff1a;https://www.bilibili.com/video/BV14j411f7DJ 目录 1.常数阶O(1) 2.对数阶O(IogN) 3.线性阶O(n) 4.线性对数阶O(nlogN) 5.平方阶O(n^2) 6.立方阶O(n^3) 7.K次方阶O(n^k) 8.指数阶(2^n) 9.阶乘O(n!) 两层for循环 for (int i 1; i <…

Linux基本指令实现4及热键指令详解

目录 Linux热键补充&#xff1a; 1.bc指令&#xff1a; Tab键的智能补充&#xff1a; ctrlc键&#xff1a; uname指令&#xff1a; lscpu指令&#xff1a; lsmem指令&#xff1a; df -h指令&#xff1a; 关机指令&#xff1a; 扩展指令&#xff1a; Linux热键补充&#…

【Linux】驱动学习,先啃框架

目录 前言&#xff1a; 一、驱动设计 &#xff08;1&#xff09;面向对象&#xff1a; &#xff08;2&#xff09;分层&#xff1a; &#xff08;3&#xff09;分离&#xff1a; 二、驱动框架 &#xff08;1&#xff09;传统框架 &#xff08;2&#xff09;总线设备驱…

STM32单片机蓝牙APP自动伸缩遮阳棚雨伞雨滴角度温度光强控制

实践制作DIY- GC0130-蓝牙APP自动伸缩遮阳棚 一、功能说明&#xff1a; 基于STM32单片机设计-蓝牙APP自动伸缩遮阳棚 二、功能介绍&#xff1a; 基于STM32F103C系列&#xff0c;LCD1602显示器&#xff0c;光敏电阻采集光强&#xff0c;雨滴传感器&#xff0c;ULN2003控制步进…

chatgpt赋能Python-pythonddos

PythonDDoS&#xff1a;了解一下这种利用Python语言的攻击方式 PythonDDoS&#xff08;Python分布式拒绝服务攻击&#xff09;是一种利用Python语言编写的DDoS攻击技术&#xff0c;它利用了Python的强大处理能力&#xff0c;可以构建高效的攻击工具&#xff0c;让攻击者能够轻…

Linux基本指令3

目录 一.基本常用指令 指令1&#xff1a;find命令&#xff1a; 指令2&#xff1a;which命令&#xff1a; 指令3&#xff1a;alias命令&#xff1a; 指令4&#xff1a;whereis which&#xff0c;find&#xff0c;whereis这三个搜索命令的区别&#xff1a; 指令5&#xff…

扫眼球换“世界币” ChatGPT之父“剥削穷人”?

ChatGPT火爆全球后&#xff0c; 山姆奥特曼&#xff08;Sam Altman&#xff09;创立的加密项目Worldcoin&#xff08;世界币&#xff09;重回大众视野。这个项目诞生于2年前。那时&#xff0c;埋头迭代GPT模型的OpenAI还未如此知名&#xff0c;该公司的CEO 山姆奥特曼也位列科技…

【中医推荐】33部中医书籍,中医医书精品(在线免费阅读),值得珍藏的国粹,涵盖中药、针灸、推拿、按摩、拔罐、气功,食疗等诸多领域

中医诞生于原始社会&#xff0c;春秋战国时期中医理论已基本形成&#xff0c;之后历代均有总结发展。除此之外对汉字文化圈国家影响深远&#xff0c;如日本医学、韩国韩医学、朝鲜高丽医学、越南东医学等都是以中医为基础发展起来的。 中医承载着中国古代人民同疾病作斗争的经…

因为一个Bug,差点损失了100w

大家好&#xff0c;我是洋子 最近在做单接口的性能测试比较多&#xff0c;在压测过程发现了一个比较有意思的问题&#xff0c;拿出来和大家分享一下 背景是这样的&#xff0c;最近在搞线上的抽奖活动&#xff0c;压测的对象是一个抽奖接口&#xff0c;主要的逻辑见程序的流程…

Vmware Linux磁盘空间扩容

Linux磁盘空间扩容 VMware虚拟机中配置&#xff08;1&#xff09;进入虚拟机设置界面&#xff0c;选择扩展磁盘容量。&#xff08;2&#xff09; 本次是在原来30G的基础上扩展为50G。 Linux中设置&#xff08;1&#xff09; 可以看出sda3是根分区&#xff0c;下面按照博客提示&…

KVM(二)命令行新建虚拟机

目录 一、准备工作 二、新建虚拟机 2.1 文件准备 2.2 正式安装 2.3 时区设置 2.4 安装设置 2.5 设置root用户密码 2.6 vm2安装完成 三、进入虚拟机vm2 四、网络设置 五、参考链接 若还未部署KVM&#xff0c;请参考第一节&#xff1a; KVM&#xff08;一&#xff09;…

python数据可视化显示(附代码)

Python是一种非常流行的编程语言&#xff0c;具有广泛的应用领域&#xff0c;包括数据可视化。在数据可视化中&#xff0c;Python提供了多种工具来帮助用户创建各种类型的图表、图形和可视化效果。本文将介绍Python数据可视化的基本概念、工具和技术&#xff0c;并提供代码示例…

(转载)MATLAB智能算法30个案例分析(1)——遗传算法工具箱

以下内容大部分来源于《MATLAB智能算法30个案例分析》&#xff0c;仅为学习交流所用。 1理论基础 1.1遗传算法概述 遗传算法(genetic algorithm,GA)是一种进化算法,其基本原理是仿效生物界中的“物竞天择、适者生存”的演化法则。遗传算法是把问题参数编码为染色体,再利用迭代…

论文阅读_语音合成_Spear-TTS

论文信息 number headings: auto, first-level 2, max 4, _.1.1 name_en: Speak, Read and Prompt: High-Fidelity Text-to-Speech with Minimal Supervision name_ch: 说话、阅读和提示&#xff1a;少量监督实现高保真文本转语音 paper_addr: http://arxiv.org/abs/2302.0354…

windows11下系统睡眠状态被UpdateOrchestrator唤醒的解决方案

windows11下系统睡眠状态被UpdateOrchestrator唤醒的解决方案 一、问题排查二、问题解决 一、问题排查 最近win11更新后发现会偶尔在睡眠状态下唤醒&#xff0c;CMD中输入powercfg -lastwake命令可以查看唤醒源程序 这里显示唤醒是按下了电源按钮&#xff0c;符合我此次唤醒操…

验证知识点总结

1、常用总线对比 AMBA (Advanced Microcontroller Bus Architecture) 高级处理器总线架构 AHB (Advanced High-performance Bus) 高级高性能总线 ASB (Advanced System Bus) 高级系统总线 APB (Advanced Peripheral Bus) 高级外围总线 AXI (Advanced eXtensible Interface) 高…

系统分析师(一)软考简介

目录 1.证书简介2.考试简介3.考试报名4.各地考试机构5.考试要求6.考试教程用书 考试时间&#xff1a; 每年5月的最后一个周六 1.证书简介 ​ 软考全称是计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff0c;是由国家人力资源和社会保障部、工业和信息化部…

华丽家族股东大会21项议案全被否

5月17日晚间&#xff0c;A股上市公司华丽家族发布关于收到上海证券交易所监管工作函的公告&#xff0c;交易所对相关事项提出监管要求。 在此之前&#xff0c;华丽家族当天召开股东大会&#xff0c;21项股东大会议案全部未通过。历史上&#xff0c;股东大会议案全部被否的情形…
最新文章