手写操作系统 --汇编执行流(二)

简介: 手写操作系统 --汇编执行流(二)

思考

  • 如何用汇编编写带参数的执行流?
  • C语言的传参,汇编层面是如何实现的?
  • 函数参数过多,汇编层面是如何实现的?

如何用汇编编写带参数的执行流

  • 考虑通过寄存器传参
  • 考虑通过栈传参
  • 考虑通过寄存器+栈传参
  • 基本结构实现还是精简结构实现?

汇编实现一个带参函数

用汇编写一个带参数的函数需要注意:

  1. 汇编层面是体现不出来有没有带参数
  2. 如果带参数,记得写函数原型
; void add(int a, int b);  
------------------------------ ;汇编函数框架
global add
add:
    push ebp
    mov ebp, esp
    leave
    ret
-------------------------------

以具体得函数举例子

#include <iostream>
int Print(int a, int b)
{
  int c = 10;
  return a + b + c;
}
int main()
{
  int a = 10;
  Print(1, 2);
  return 0;
}
;对应cpp汇编
    16:   Print(1, 2);
001C258F  push  2  
001C2591  push  1           ;从右向左
001C2593  call  001C1384  
001C2598  add   esp,8   ;外平栈   堆栈平衡   
 6: int Print(int a, int b)
     7: {
001C16F0  push ebp  
001C16F1  mov  ebp,esp          ;创建栈帧
001C16F3  sub  esp,0CCh  
-------------------------------------------
001C16F9  push ebx  
001C16FA  push esi        ;保存上下文
001C16FB  push edi  
-------------------------------------------
001C16FC  lea  edi,[ebp+FFFFFF34h]  
001C1702  mov  ecx,33h  
001C1707  mov  eax,0CCCCCCCCh           ;初始化栈
001C170C  rep stos dword ptr es:[edi]  
-------------------------------------------
     8:   int c = 10;
001C1718  mov  dword ptr [ebp-8],0Ah  
     9: 
    10:   return a + b + c;     ;业务逻辑
001C171F  mov eax,dword ptr [ebp+8]  
001C1722  add eax,dword ptr [ebp+0Ch]  
001C1725  add eax,dword ptr [ebp-8]  
-------------------------------------------
    11: }
001C1728  pop edi  
001C1729  pop esi         ;恢复上下文
001C172A  pop ebx  
-------------------------------------------
001C1738  mov esp,ebp  
001C173A  pop ebp            ;释放栈帧
-------------------------------------------
001C173B  ret  

根据对应代码回答几个问题:

  • 当采用MVC编译时,虽然没有写调用约定,但是默认的调用约定是__cdecl
  • 用时,参数的压栈顺序是从右向左,还是从左向右?
  • 依靠栈传参
  • 传参的顺序:从右向左(所有的调用约定)
  • 传递的参数是在哪个栈里?_tmain(调用者)还是print(被调用者)?
  • 调用者栈中_tmain
  • print函数中怎么取到参数?
  • 借助ebp寄存器
  • 第一个参数的计算公式要记住:ebp+8ebp + 0xc
    32位:ebp + 4 * 2(非常重要!!!)
    64位:rbp + 8 * 2
  • 因为传参破坏了栈平衡,由谁来平栈
  • 内平栈:被调用函数自己来平衡栈,如ret 4 * N ,其中N为参数个数
  • 外平栈:调用者来平衡栈

上文描述的汇编执行流图:

关于传参

  • 用者传参,汇编层面是如何实现的?
  • 被调用者如何拿到参数,汇编层面是如何实现的
  • 传参与局部变量,汇编层面的实现,是否是一样的?

函数调用约定

  • __cdecl
  • __stdcall
  • __fastcall

x86模式下

对于x86架构(32位),GCC默认使用__cdecl作为其调用约定。cdecl(C declaration)是最常见的调用约定,特别是在Unix、Linux和其他POSIX系统上。

于32位Windows(x86架构),许多Windows API函数使用stdcall作为其默认调用约定。

  • __cdecl
  • 传参方式及传参顺序
  • 只借助栈
  • 自右向左
  • 平栈的方式
  • 外平栈
  • __stdcall
  • 传参方式及传参顺序
  • 只借助栈
  • 自右向左
  • 平栈的方式
  • 内平栈
// 采用__stdcall调用约定
#include <iostream>
int __stdcall Print(int a, int b)
{
  int c = 10;
  return a + b + c;
}
int main()
{
  int a = 10;
  Print(1, 2);
  return 0;
}
; 对比__cdecle调用约定的主要代码
    16:   Print(1, 2);
0005258F 6A 02                push  2  
00052591 6A 01                push  1  
00052593 E8 F1 ED FF FF       call  00051389  
;没有了 add esp,8的外平栈  
6: int __stdcall Print(int a, int b)
{....
0005173B C2 08 00             ret 8      ;内平栈
  • __fastcall
  • 传参方式及传参顺序
  • 会借助寄存器传参 总计6 + 8 = 14个寄存器
  • 参数 <= 2纯寄存器传参
  • 参数 > 2寄存器 + 栈的方式传参,用了两个寄存器:ecxedx
  • 自右向左,edx第二个参数,ecx第一个参数
  • 平栈的方式
  • 纯寄存器传参时不需要平栈
  • 寄存器 + 栈传参时采用内平栈

只有两个参数使用纯寄存器传参:

// 采用__fastcall调用约定
#include <iostream>
int __fastcall Print(int a, int b)
{
  int c = 10;
  return a + b + c;
}
int main()
{
  int a = 10;
  Print(1, 2);
  return 0;
}
; 对比__cdecle调用约定的主要代码
    16:   Print(1, 2);
0101258F BA 02 00 00 00       mov  edx,2      ;使用寄存器传参
01012594 B9 01 00 00 00       mov  ecx,1  
01012599 E8 F0 ED FF FF       call 0101138E  
;没有了 add esp,8的外平栈  
int __fastcall Print(int a, int b)
     7: {  .....
01013D13 C3                   ret     ;不需要平栈

超过两个参数使用寄存器 + 栈的方式传参:

  • 用了两个寄存器:ecx、edx
  • 自右向左
  • edx是第二个参数,ecx第一个参数
  • 前两个参数用寄存器传参,后两个参数用栈传参
  • 内平栈
// 采用__fastcall调用约定
#include <iostream>
int __fastcall Print(int a, int b)
{
  int c = 10;
  return a + b + c;
}
int main()
{
  int a = 10;
  Print(1, 2);
  return 0;
}
; 对比只有两个参数时使用__fastcall调用约定的主要代码
    16:   Print(1, 2, 3, 4);
0055258F 6A 04                push 4  
00552591 6A 03                push 3    ;前两个参数用寄存器传参,后两个参数用栈传参
00552593 BA 02 00 00 00       mov  edx,2      
00552598 B9 01 00 00 00       mov  ecx,1  
0055259D E8 F1 ED FF FF       call 00551393  
int __fastcall Print(int a, int b)
     7: {  .....
00553D13 C2 08 00             ret  8     ;内平栈

x64模式下

对于x64架构,情况有所不同。x64平台基本上统一了函数调用约定,不同于x86有多种调用约定。在Unix-like系统(如Linux)上,x64使用System V ABI,而在Windows上使用x64 calling convention。这意味着在x64上,不论是GCC还是其他编译器,都使用相同的调用约定。

  • __fastcall
  • 传参方式及传参顺序
  • 会借助寄存器传参
  • 参数 <= 6纯寄存器传参
  • 参数 > 6寄存器 + 栈的方式传参
  • 前六个整数或指针参数传递给RDI, RSI, RDX, RCX, R8, R9
  • 前八个浮点参数传递给XMM0XMM7
  • 超过这些限制的参数通过堆栈传递。
在x64架构下,与x86相比,函数调用约定已经得到了简化。在Windows和Unix-based系统(如Linux)下的x64函数调用约定有所不同。以下是两者的简要概述:
### Windows x64 Calling Convention  (__fastcall): 
1. **寄存器传参**:
   - 前四个整数或指针参数传递给`RCX`, `RDX`, `R8`, `R9`。
   - 前四个浮点参数传递给`XMM0`, `XMM1`, `XMM2`, `XMM3`。
   - 如果有更多的参数,它们将通过堆栈传递。
2. **调用者保存寄存器**:
   - 调用者负责保存`RAX`, `RCX`, `RDX`, `R8`, `R9`, `R10`, `R11`以及`XMM0`到`XMM5`。
3. **被调用者保存寄存器**:
   - 被调用函数(如果它们被修改)负责保存`RBX`, `RSI`, `RDI`, `RSP`, `RBP`, `R12`到`R15`,以及`XMM6`到`XMM15`。
4. **堆栈对齐**:
   - 必须保证堆栈(RSP)在函数调用前是16字节对齐的。
### System V ABI (Unix-based, e.g., Linux) x64 Calling Convention:
1. **寄存器传参**:
   - 前六个整数或指针参数传递给`RDI`, `RSI`, `RDX`, `RCX`, `R8`, `R9`。
   - 前八个浮点参数传递给`XMM0`到`XMM7`。
   - 超过这些限制的参数通过堆栈传递。
2. **调用者保存寄存器**:
   - 调用者负责保存`RAX`, `RCX`, `RDX`, `RSI`, `RDI`, `R8`, `R9`, `R10`, `R11`以及`XMM0`到`XMM15`。
3. **被调用者保存寄存器**:
   - 被调用函数(如果它们被修改)负责保存`RBX`, `RSP`, `RBP`, `R12`到`R15`。
4. **堆栈对齐**:
   - 与Windows一样,堆栈(RSP)在函数调用前也必须是16字节对齐的。
以上仅仅是函数调用约定的基本点。


相关文章
|
4月前
|
存储 Java C语言
手写操作系统 --汇编执行流(一)
手写操作系统 --汇编执行流(一)
|
4月前
|
C语言
手写操作系统 - 汇编实现进入保护模式
手写操作系统 - 汇编实现进入保护模式
超详细汇编注释 操作系统实验二 操作系统的引导(哈工大李治军)(二)
超详细汇编注释 操作系统实验二 操作系统的引导(哈工大李治军)(二)
128 0
超详细汇编注释 操作系统实验二 操作系统的引导(哈工大李治军)(二)
|
存储 Ubuntu Linux
超详细汇编注释 操作系统实验二 操作系统的引导(哈工大李治军)(一)
超详细汇编注释 操作系统实验二 操作系统的引导(哈工大李治军)(一)
175 0
超详细汇编注释 操作系统实验二 操作系统的引导(哈工大李治军)(一)
|
程序员 C语言 编译器
《操作系统真象还原》——0.16 为什么说汇编语言比C语言快
不管用什么语言,程序最终都是给CPU运行的,只有CPU才能让程序跑起来。CPU不知道什么是汇编语言、C语言,甚至Java、PHP、Python等,它根本不知道交给它的指令曾经经历过那么多的解释、编译工序。不管什么语言,编译器最终翻译出来的都是机器指令。
2615 0
|
21天前
|
监控 Unix Linux
Linux操作系统调优相关工具(四)查看Network运行状态 和系统整体运行状态
Linux操作系统调优相关工具(四)查看Network运行状态 和系统整体运行状态
32 0
|
22天前
|
Linux 编译器 开发者
Linux设备树解析:桥接硬件与操作系统的关键架构
在探索Linux的庞大和复杂世界时🌌,我们经常会遇到许多关键概念和工具🛠️,它们使得Linux成为了一个强大和灵活的操作系统💪。其中,"设备树"(Device Tree)是一个不可或缺的部分🌲,尤其是在嵌入式系统🖥️和多平台硬件支持方面🔌。让我们深入了解Linux设备树是什么,它的起源,以及为什么Linux需要它🌳。
Linux设备树解析:桥接硬件与操作系统的关键架构
|
2月前
|
Linux 数据安全/隐私保护 虚拟化
Linux技术基础(1)——操作系统的安装
本文是龙蜥操作系统(Anolis OS) 8.4 的安装指南,用户可以从[龙蜥社区下载页面](https://openanolis.cn/download)获取ISO镜像。安装方法包括物理机的光驱和USB闪存方式,以及虚拟机中的VMware Workstation Pro设置。安装过程涉及选择语言、配置安装目标、选择软件集合和内核,设置Root密码及创建新用户。安装完成后,可通过文本模式或图形化界面验证系统版本,如Anolis OS 8.4,标志着安装成功。