惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
C
Cisco Blogs
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
V2EX - 技术
V2EX - 技术
H
Heimdal Security Blog
S
Security Affairs
L
Lohrmann on Cybersecurity
Hacker News - Newest:
Hacker News - Newest: "LLM"
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
小众软件
小众软件
Security Latest
Security Latest
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
罗磊的独立博客
F
Full Disclosure
S
Schneier on Security
L
LangChain Blog
MyScale Blog
MyScale Blog
Know Your Adversary
Know Your Adversary
P
Privacy International News Feed
Google Online Security Blog
Google Online Security Blog
Scott Helme
Scott Helme
Stack Overflow Blog
Stack Overflow Blog
爱范儿
爱范儿
A
Arctic Wolf
Martin Fowler
Martin Fowler
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
The Register - Security
The Register - Security
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
Latest news
Latest news
F
Fortinet All Blogs
G
GRAHAM CLULEY
T
The Exploit Database - CXSecurity.com
Hacker News: Ask HN
Hacker News: Ask HN

Posts on Changkun's Blog

Why High-Output Systems Are Often the First to Stop Growing Dark Forest Theory: A Formal Derivation Agents (or Humans) in Goal-Directed and Goalless Environments: On Pipelines, Priors, and the Rhythm Between Exploration and Exploitation At the Boundary of Self-Reference: From Stable Structures in Artificial Intelligence to the Self as a Recursive Model in an Open Dissipative System 2023 Reading List 2022 Reading List 2021 Reading List Are PSS/USS and RSS Actually the Same Thing? Performance Differences from Page Faults vs. Prefetching 2020 Year-End Review Migration with Zero Downtime 2020 Reading List The All in Go Stack Pointers Might Not Be Ideal for Parameters Eliminating A Source of Measurement Errors in Benchmarks Setup Wordpress in 10 Minutes 我为什么不再写博客了? 2019 年终总结 2018-2019 读书清单 Ten years of blogging Rethinking the Reflections on Communications and Trusts 2018 年终总结 Go source code study is open source Go source study: unsafe Pattern Go source study: sync.Pool Go runtime programming A Million WebSocket and Go Designing Asynchronous RESTful APIs 分布式杂谈01:CAP 理论的误解 Issues of Human-Bot Interaction 压缩法与深度网络的泛化性 Go in 1 Hour UMSLT04: The Past and Present of SGD UMSLT03: A Gentle Start of Learning Theory UMSLT02: A Breif History of Neural Networks UMSLT01: A Breif History of Regularization 不笑不足以为道 论文笔记:Generalization in Deep Learning 2017 年终总结 2017 读书清单 深度学习的泛化理论简介 删除 GitHub 上已经提交的敏感信息 硕士生涯的第一年就这样告一段落了 人肉计算(10): 系统参与激励 人肉计算(9): 陷阱的解法 别聊,一聊你就暴露 人肉计算(8): 人肉计算与数据科学中的陷阱 人肉计算(7): 社会行为分析 Hexo + GitHub + Travis CI + VPS 自动部署 人肉计算(6): 预测市场 人肉计算(5): 信用风险评级模型 读书与回报 瞎扯: 对现代企业理论与当下IT企业的商业模式和信息产业链的规律性的思考 人肉计算(4): 输入数据聚合与PageRank 又一次打整了一下博客 人肉计算(3): 输入数据聚合与链路预测 人肉计算(2): 意图博弈 GWAPs 人肉计算(1): 众包与群众智慧 对后辈同学在计算机专业上的答疑与解惑 在德国的医疗及住院体验 这可能不是一个技术博客了 实验楼楼赛第3期-Python-题解 迅速更换了 DISQUS Electron 深度实践总结 良好的编码体验的三个方面 2016 年终总结 2016 读书清单 最近在着手写的文章 微信小程序文档极致总结 谈谈过去三个月在实验楼的实习经历 Built a Desktop Client for My Blog Guacamole 源码分析与 VNC 中 RFB 协议的坑 《高速上手 C++11/14》正式发布 Docker 极速入门教程02 - 镜像与容器管理 Docker 极速入门教程01 - 基本概念和操作 阶段性沉默 ELK+Redis 最佳实践 终于全面启用了 HTTPS 苹果开源了LZFSE无损压缩 Hash 碰撞的一种思路 记一次完整的 Kaldi-TIMIT 示例运行 Kaldi 上的 TIMIT 例子 Kaldi 安装与部署 从科研写作谈起 Swift API 设计指南 有趣的人类 所以其实论文并没有什么鬼用 Githug 通关记录及指南 小结一下这学期的收获 2015 读书清单 2015 年终总结 负能量爆表 转眼就快两个月了 博客迁移记录 大三总结 这个世界,终究不会是我们的。 Linux 内核分析 之六:Linux 内核创建进程的过程 小说「泽缘」 Linux 内核分析 之五:system_call中断处理过程的简要分析 大创项目的标题真是每年都在考验同学们的想象力啊
Linux 内核分析 之一:How Computer Works 实验
Changkun Ou · 2015-03-04 · via Posts on Changkun's Blog

Published at发布于:   |   PV/UV: /   |   Reading阅读: 3 min

说明 欧长坤 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 这学期学校恰好有操作系统的课程,上个学习就开始寻思研究研究Linux内核代码,恰好MOOC有这个课程,遂选了此课。

一、实验过程 首先,我们将C语言代码利用编译器编译成汇编代码,下面是C语言代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int g(int x)
{
  return x + 999;
}
int f(int x)
{
  return g(x);
}
int main(void)
{
  return f(0) + 1;
}

值得一提的是,事实上在我们没有使用标准C语言库的时候,可以不引用任何头文件(比如#include 就是不需要的)。显然,上面的代码并没有用到任何库函数。

通过gcc的功能,我们可以自定义编译参数来控制编译选项,我们为了让上面的代码编译成32位汇编代码,使用下面的命令:

1
gcc -S -o main.s main.c -m32

其中我们的编译环境为Mac OS X 10.10,所以提供的编译环境为64位编译环境,所以使用了参数-m32将C语言代码编译为32位汇编代码,而-S表示只是编译不汇编,生成汇编代码。而-o file表示将结果输出到file中。

我们可以观察生成的.s文件,如下图所示。

bwlq分别代表8、16、32、64位寄存器操作,所以我们可以看到这里的汇编指令l结尾,说明这些指令都是操控32位寄存器长度。

我们保留纯汇编代码的部分,得到如下的汇编代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
_g:
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%eax
	movl	8(%ebp), %eax
	movl	%eax, -4(%ebp)
	movl	-4(%ebp), %eax
	addl	$999, %eax              ## imm = 0x3E7
	addl	$4, %esp
	popl	%ebp
	retl
_f:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$8, %esp
	movl	8(%ebp), %eax
	movl	%eax, -4(%ebp)
	movl	-4(%ebp), %eax
	movl	%eax, (%esp)
	calll	_g
	addl	$8, %esp
	popl	%ebp
	retl
_main:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	movl	$0, %eax
	movl	$0, -4(%ebp)
	movl	$0, (%esp)
	movl	%eax, -8(%ebp)          ## 4-byte Spill
	calll	_f
	addl	$1, %eax
	addl	$24, %esp
	popl	%ebp
	retl

下面我们来分析这段代码的行为。

二、汇编代码行为分析

在分析这段汇编代码之前,我们需要牢记以下几点,不然分析起来就会对代码的各种诡异行为产生困惑,从而导致无法理解代码。 另外,这里的编译平台是Mac OS X,虽然使用的是gcc编译器,但是实际上在Mac中表现为clang的编译器,所以得到的汇编代码会和老师所分析的代码有很大差异。

注意以下六点知识:

  1. 堆栈分配是从高地址往低地址分配的,所以subl
  2. eipip 全称 ip = instruction pointer 指令指针,用来存放当前指令的下一条指令地址;
  3. ebp 是堆栈指针,指向栈底;
  4. esp 是堆栈指针,指向栈顶;
  5. 函数调用堆栈实际上是逻辑上多个堆栈所叠加而成的;
  6. 函数的返回值默认使用eax寄存器存储返回给上一级函数。

汇编代码和C语言一样,都是从main函数开始的,所以我们在执行上面的汇编代码时,是从_main标签开始的。

首先,我们知道,main函数实际上是在系统的管理下分配出了另外一个内存空间来运行当前的函数,所以根据第五点知识,函数调用堆栈实际上是逻辑上多个堆栈所叠加而成,main函数的堆栈,会叠加在上一层函数(系统)。因此,在执行_main之前,系统的汇编代码就会表现为 call _main 状态,并且在堆栈中压入 eip 的值,这时 eip 的值是系统在执行完这个main函数的时候进行的下一步处理指令的地址。

我们假设不知道当前 ebpesp 的位置,不妨设成如下图所示。

那么,第一句pushl %ebp,会再esp的位置压入ebp的的值,这时,在1FF0H这个位置会存入2000H,并且esp会移动到1FF0H的位置(堆栈分配从高地址往底地址分配,esp进行push时,指针会做减法,pop时,指针会做加法),接下来movl %esp, %ebp 会将esp的值传给ebp,因此,这时候ebp来到了esp的位置1FF0H。

接下来的这步subl $24, %esp很诡异,可能是clang编译器对每个堆栈分配的时候都会预留24,我们先不去管它,直接对堆栈指针进行变动,这条让esp指针下移了24位。然后的四句:

1
2
3
4
movl $0, %eax ## eax = 0
movl $0, -4(%ebp) ## 这时候 ebp - 4 的位置也被赋值为0
movl $0, (%esp) ## esp 所指向的单元也被赋值为了0
movl %eax, -8(%ebp) ## ebp - 8的位置,被赋值为了eax,也是0

得到下面图所示:

好,接下来这一步是 calll _f,这时候,会往堆栈中压入eip的值,而此时 eip 的值是 call _f的下一条指令addl $1, %eax的地址,我们不妨设为 X。 我们来到 _f 函数,

1
2
pushl %ebp      ## 这时候继续压入ebp的值
movl %esp, %ebp ## 同样的,把栈底的位置移动到当前esp的位置上去

然后呢,这个堆栈似乎并没有如同main函数那样,clang编译器只给预留了8个单位的空间,所以esp会继续向下移动两个单元,并且,我们会把这时候ebp的前两个单元的值赋值给eax,

1
2
3
4
5
subl $8, %esp ## esp = 1FC8H
movl 8(%ebp), %eax ## eax = 0
movl %eax, -4(%ebp) ## (1FCCH) = 0
movl -4(%ebp), %eax ## eax = 0
movl %eax, (%esp) ## (1FC8H) = 0

我们就可以得到如下图所示:

那么

1
2
3
4
5
calll _g
这时候继续增加堆栈,进入函数g,这时候又会压入下一条命令 addl $8, %esp 的地址Y,然后开始执行函数 _g。现在我们来到函数 _g 
pushl %ebp
movl %esp, %ebp
pushl %eax

继续压入ebp的值,把esp的值给ebp,同时把eax的值压入堆栈中。 然后,会把ebp前面两个位置的值赋值给eax = 0,并且把eax的值赋值给ebp,而ebp下面那个单元的值又会赋值给eax(又是好诡异)

1
2
3
movl 8(%ebp), %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax

无论如何,eax的值始终是0,到这,如图所示:

接下来,

1
2
3
addl $999, %eax ## eax = 0 + 999 = 999
addl $4, %esp ## esp = 1FC0H
popl %ebp

eax的值会加上999,esp会向上移动一格,然后弹栈,把1FD0H值赋值给ebp。这时候esp = 1FC4H

接下来

返回。这时候会弹出esp所在位置的值赋值给eip,而esp会增四。 这时,ebp = 1FD0H, esp =1FC8H, eip =Y,eax = 999,所以下一步将会执行Y地址的指令,我们上面假设了,Y地址的指令恰好就是函数f的命令addl $8, %esp。所以我们从这里开始:

1
2
addl $8, %esp
popl %ebp

esp会继续向上移动两个格子,同时出栈,把1FF0H的值赋值给ebp,如图所示。

这时候我们再retl,会继续出栈,eip会得到X的值,esp会移动到1FD8H的位置 我们刚才假设过,X地址的指令,其实就是main函数addl $1, %eax指令的位置,所以这时候

结果为:eax = 999 + 1 = 1000。

接下来,esp加24,回到了最初的位置1FF0H, 最后,popl %ebp,继续弹栈,ebp回到了2000H的位置。 得到:

通过retl,完成了整个main函数的运行,而eax寄存器里面的内容,则作为默认的返回值,最终返回给系统了,这时eax的值为:1000。符合我们所编译的C语言代码的main函数的预期返回值。说明我们的分析是正确地。

三、总结

从上面的过程我们可以看出,计算机中,函数调用的这部分工作实际上是通过堆栈的层层堆叠完成的。我们编写的程序,同样也是在系统上进一步堆叠一个堆栈从而执行。我们完成了从代码层面上得详细分析。 下面谈谈我个人对计算机内部的函数调用模型的个人理解:

计算机的工作过程在最简单的情况下,只涉及两个硬件——CPU和内存。在冯诺依曼体系下,CPU在一个时刻内,从内存中取出执行的指令,送往内部的控制器进行指令的解释执行。 与此同时,在内存中存放的不仅仅是指令,还有CPU在执行指令时候的数据,因此,指令和数据在内存中实际上是混合排布在一起的,区分他们的唯一方式就是这个单元的数字如果被送往控制器,则为指令,否则为数据。 在操作系统管理内存的时候,堆栈是从高地址往低地址进行分配,堆栈也是如此堆叠而成(我目前还不清楚是否有意外)。目的是为了和低内存地址位置的系统数据和指令加以区分,防止出现意外。 计算机的CPU通过CPU内部的几个寄存器,来记录并模拟堆栈的行为。当在一个函数A中调用另一个函数B时,实际上是把指令指针eip的地址压入堆栈来确保以后能成功的返回上一级堆栈,然后从函数B的入口地址进入转去执行函数B,当函数B执行完后,此时弹出堆栈时,eip会接受弹栈的值,并返回去执行函数A没有执行完的内容。

将以上过程放大到整个计算机来看,四个字:“取值,执行”,这就是计算机的工作原理。