[C 语言笔记--Day04] C 语言的 function call 如何被组合语言实作

这篇文章是用 x86-64 的架构作为例子,因为多数人的电脑是用 x86-64 的架构
只要依照 在linux中看gcc产生出来的组合语言 这篇文章
就可以把 C 语言的档案编议成 x86-64 的组合语言
编译时请记得加上 -Og 这个 flag ,因为较低的优化程度比较好观察 gcc 的行为

大纲:

  1. x86-64 有哪些 register
  2. 一个没有回传值、也没有参数的 function
  3. 如何回传值
  4. 如何传参数
  5. 参数大於 6 个时该怎麽做?

x86-64 有哪些 register

registers of x86-64
如上图,x86-64 共有 16 个 64-bit register ,(不过这张图并没有把 program counter 画上去)

以图中左上的 register 为例,
%rax 代表的是整个 64-bit ,
而写 %eax 时,代表的是较低位元的 32-bit

除了用红色标示的 %rsp 有特别的用途之外 (register stack pointer),
其他的 15 个 register 都算是 general purpose 的 register

一个没有回传值、也没有参数的 function

这种状况是最简单的,考虑以下的 C 语言程序码:

void callee()
{
    /*...
    ...*/
}

void caller()
{
    /*...
    ...*/

    callee();

    /*...
    ...*/
}

编译成组合语言:

callee:
    ...
    ...
    ...
    ...
    ret     

caller:
    ...
    ...
    call    callee
    ...
    ...

call callee 这行代表让 program counter 指到 callee 的位置
ret 这行代表回到 call callee 的下一行继续执行
几乎就跟 C 语言的运作逻辑相同

如何回传值

回传值的过程大概可以列成以下步骤:

  1. caller 呼叫 callee
  2. callee 执行到最後面时,把回传值放到 %rax
  3. callee 执行 ret 回到 caller 继续执行
  4. caller%rax 拿取回传值(因为 callee 已经把回传值放到%rax 了)

透过 callee 把回传值放到 %rax
caller%rax 拿取回传值的模式,
就成功的实作出 C 语言里 function 回传值的功能,
当然不用 %rax 用其他的 register 也可以
但习惯上就是会用 %rax
所以说 C 里的 return value 并不是用硬体来实作的行为,而是一种软件上的设计

考虑以下的 C 语言程序码:

long callee()
{
    return -0x87;
}

long caller() 
{
    long result;
    result = callee() + 1;
    return result;
}

编译出的组合语言:

callee:
    mov    $0xffffffffffffff79,%rax
    retq   

caller:
    callq  callee
    add    $0x1,%rax
    retq   

如何传参数

传参数的方法跟传回传值的方法差不多
就跟回传时要约定好要用 %rax 来当中介一样
传参数也要约定好要用哪些 register 来中介:

第几个参数 register
1 %rdi
2 %rsi
3 %rdx
4 %rcx
5 %r8
6 %r9

考虑以下的 C 语言程序码:

long callee(long a1, long a2, long a3, long a4, long a5, long a6)
{
    return a1 + a2 + a3 + a4 + a5 + a6;
}

long caller() 
{
    long result, a1, a2, a3, a4, a5, a6;
    a1 = -1;
    a2 = -2;
    a3 = -3;
    a4 = -4;
    a5 = -5;
    a6 = -6;
    result = callee(a1, a2, a3, a4, a5, a6) - 0x87;
    return result;
}

编译出的组合语言:

callee:
    add    %rsi,%rdi
    add    %rdx,%rdi
    add    %rcx,%rdi
    add    %r8,%rdi
    lea    (%rdi,%r9,1),%rax            (%rax = %rdi + %r9 * 1)
    retq   

caller:
    mov    $0xfffffffffffffffa,%r9      (two's complement)
    mov    $0xfffffffffffffffb,%r8
    mov    $0xfffffffffffffffc,%rcx
    mov    $0xfffffffffffffffd,%rdx
    mov    $0xfffffffffffffffe,%rsi
    mov    $0xffffffffffffffff,%rdi
    callq  40 <caller+0x2f>
    sub    $0x87,%rax
    retq   

参数大於 6 个时该怎麽做?

来不及发文了,这个就留到明天在说!

参考资料:

Computer Systems: A Programmer's Perspective, 3/E (CS:APP3e)


<<:  Day1:第一天来点简单的先安装Parrot_Security

>>:  阻止B-1B轰炸机前进的不是敌人是一台平板

【Day03】渲染元素 Rendering Element

React 的核心之一是 JSX 语法, 这意味着整个网页内容,包含 HTML 与 CSS, 基本上...

数据中台架构

包含基础设施、架构设计、资料采集(ETL)、主资料管理(MDM)、即时计算、资资料储存和作业排程等。...

学习Ruby、Rails事前准备工作

专有名词 整理了我觉得该先了解的一些专有名词 wiki-物件导向程序设计、菜鸟式回答:是一种将资料,...

[30天 Vue学好学满 DAY25] axios API

vue.js2.0後版本推荐使用axios来完成ajax请求 为Promise-based HTTP...

【C#】Creational Patterns Abstract Factory Mode

The Abstract Factory pattern provides an interface...