简单C程序在IA-32 CPU上运行过程的分析

本文将通过编译器生成的汇编代码分析C程序在IA-32体系PC上的运行流程

实验环境: gcc 4.8.2

C语言程序的内存结构

C代码如下

int g(int x)
{
    return x + 1;
}

int f(int x)
{
    return g(x);
}

int main(void)
{
    return f(2) + 3;
}

使用编译命令gcc -S -O0 -o main.s main.c -m32编译出汇编文件,如下

g:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    addl    $1, %eax
    popl    %ebp
    ret
f:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    g
    leave
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    $2, (%esp)
    call    f
    addl    $3, %eax
    leave

入口点位于main,由main开始分析

  • pushl %ebp

    • subl $4, %esp
    • movl %ebp, (%esp)
    • 这是进入main函数后的第一个操作,保存了原来的ebp,即main开始执行之前的栈现场
  • movl %esp, %ebp
    • esp的值进入ebp,这儿将栈底设置为进入main之前的栈顶,即此处开始,ebp后面新增的栈都是main函数中的操作造成的,每次ebp的变化都意味着当前所处的函数的变化
  • subl $4, %esp
  • movl $2, (%esp)
    • 以上两句实际上是执行了pushl $2, %esp,将立即数2入栈,
    • 这句执行完成以后,使用gdb查看eip的值为0x8048418<main+13>,即下一行call f
  • call f
    • pushl %eip
    • jmp f
    • 调用f函数,上面两句入栈的立即数2就是函数f的参数,并将eip指向f的入口地址
  • addl $3, %eax
    • eax中存储的是函数f调用返回的结果,这条语句在eax上加上了立即数3
  • leave
    • movl %ebp, %esp
    • popl %ebp
    • 这两句将ebp还原到了调用main之前的现场,main函数调用到此为止
  • ret
    • popl %eip
    • 将eip指回调用main之前的地址,退出函数main

进入main以后的第一个函数调用f(x)

  • pushl %ebp
  • movl %esp, %ebp
    • 这儿的作用和main中一样,不重复说明
  • subl $4, %esp
    • 为参数留出栈空间
  • movl 8(%ebp), %eax
    • 栈地址是向下增长的,这儿ebp+8是获取了压入eip和参数前的地址,这条语句就是将参数存入eax
  • movl %eax, (%esp)
    • eax入栈,即参数入栈,c代码中函数g的参数就是函数f接收的参数,所以直接调用g函数
  • call g
    • pushl %eip
    • jmp g
  • leave
    • 以上两行和main一致,不再重复
  • ret
    • 和main中作用一致

最后一个函数调用g(x)

  • pushl %ebp
  • movl %esp, %ebp
    • 和前面描述一致,不赘述
  • movl 8(%ebp), %eax
    • 依然和f中的功能一致
  • addl $1, %eax
    • 函数g的功能,即在参数上+1
  • popl %ebp
    • movl (%esp), %ebp
    • addl $4, %esp
    • 恢复调用g之前的栈
  • ret
    • popl %eip
    • 将eip指回调用函数g之前的地址,即回到函数f中

C语言程序在IA-32机器上的运行过程中,最重要的几条指令即push/pop,call/ret,其中push/pop实现了栈的基本操作,call/ret维护了函数的调用栈,确保了函数调用流程的连续

使用ddd抓了两张call前后的eip变化,可以很清晰的看出eip在call时的非线性改变

最后再看一下编译器优化可以做到的效果,以gcc -S -O3 -o test.s test.c -m32编译上述代码,得到的汇编如下

g:
    movl    4(%esp), %eax
    addl    $1, %eax
    ret
f:
    movl    4(%esp), %eax
    addl    $1, %eax
    ret
main:
    movl    $6, %eax
    ret

可以看见编译器直接把main函数的结果算出来当做立即数返回了。。还是很强大的。。f和g这种简单函数也没有重新设置栈底,而是直接采用了变址寻址,提升了运行效率

云课堂作业

吴韬,原创作品转载请注明出处,《Linux内核分析》MOOC课程

:first-child {
margin-top: 0;
}
blockquote > :last-child {
margin-bottom: 15px;
}
h1 {
text-transform: uppercase;
font-weight: bold;
border-bottom: 1px solid;
}
h2 {
border-bottom: 1px solid;
}
h3,
h4,
h5,
h6 {
border-bottom: none;
}
html * {
color: #657b83;
}
html body {
background-color: #fdf6e3;
}
html h1,
html h2,
html h3,
html h4,
html h5,
html h6 {
color: #586e75;
border-color: #657b83;
}
html a,
html a:active,
html a:visited {
color: #586e75;
}
html a:hover {
background-color: #eee8d5;
}
html pre {
color: #586e75;
background-color: #eee8d5;
}
html a,
html a:active,
html a:visited,
html code.url {
color: #b58900;
}
html h1 {
color: #b58900;
}
html h2,
html h3,
html h4,
html h5,
html h6 {
color: #b58900;
}

@media print {
body {
margin: 0;
}
* {
color: #000 !important;
}
}
-->
code[class*="language-"],
pre[class*="language-"] {
background-color: #fdfdfd;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
position: relative;
padding: .2em;
-webkit-border-radius: 0.3em;
-moz-border-radius: 0.3em;
-ms-border-radius: 0.3em;
-o-border-radius: 0.3em;
border-radius: 0.3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, 0.1);
}

pre[class*="language-"]:before,
pre[class*="language-"]:after {
content: ‘‘;
z-index: -2;
display: block;
position: absolute;
bottom: 0.75em;
left: 0.18em;
width: 40%;
height: 20%;
-webkit-box-shadow: 0px 13px 8px #979797;
-moz-box-shadow: 0px 13px 8px #979797;
box-shadow: 0px 13px 8px #979797;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}

:not(pre) > code[class*="language-"]:after,
pre[class*="language-"]:after {
right: 0.75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}

.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #7D8B99;
}

.token.punctuation {
color: #5F6364;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.function-name,
.token.constant,
.token.symbol,
.token.deleted {
color: #c92c2c;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.function,
.token.builtin,
.token.inserted {
color: #2f9c0a;
}

.token.operator,
.token.entity,
.token.url,
.token.variable {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}

.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name {
color: #1990b8;
}

.token.regex,
.token.important {
color: #e90;
}

.language-css .token.string,
.style .token.string {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}

.token.important {
font-weight: normal;
}

.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}

.token.entity {
cursor: help;
}

.namespace {
opacity: .7;
}

@media screen and (max-width: 767px) {
pre[class*="language-"]:before,
pre[class*="language-"]:after {
bottom: 14px;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}

}

/* Plugin styles */
.token.tab:not(:empty):before,
.token.cr:before,
.token.lf:before {
color: #e0d7d1;
}

/* Plugin styles: Line Numbers */
pre[class*="language-"].line-numbers {
padding-left: 0;
}

pre[class*="language-"].line-numbers code {
padding-left: 3.8em;
}

pre[class*="language-"].line-numbers .line-numbers-rows {
left: 0;
}
-->

时间: 2024-10-10 20:59:10

简单C程序在IA-32 CPU上运行过程的分析的相关文章

linux top cset schedtool 对于多核CPU,如何限制进程在一个CPU上运行

对于多核CPU,如何限制进程在一个CPU上运行呢? 如何察看某个进程在哪个CPU上运行: 在控制台中输入: #top -d 1 之后按下f.进入top Current Fields设置页面: 选中:j: P          = Last used cpu (SMP) 则多了一项:P 显示此进程使用哪个CPU. 经过试验发现:同一个进程,在不同时刻,会使用不同CPU Core.这应该是Linux Kernel SMP处理的. 本程序通过这个方法查看,将会在多个CPU上运行. 想要让它在一个CPU

vcpupin和taskset命令设置某虚拟机在某个固定cpu上运行

Taskset命令设置某虚拟机在某个固定cpu上运行 1)设置某个进程pid在某个cpu上运行: [[email protected]~]# taskset -p000000000000000000000000000000000000100 95090 pid 95090's current affinity mask: 1 pid 95090's new affinity mask: 100 解释:设置95090这个进程,在cpu8上运行 95090是我提前用ps –aux|grep "虚拟机

如何将.Net Core应用程序部署在Linux操作系统上运行

.Net Core简介 跨平台: 可以在 Windows.macOS 和 Linux 操作系统上运行. 跨体系结构保持一致: 在多个体系结构(包括 x64.x86 和 ARM)上以相同的行为运行代码. 命令行工具: 包括可用于本地开发和持续集成方案中的易于使用的命令行工具. 部署灵活: 可以包含在应用或已安装的并行(用户或系统范围安装)中. 可搭配 Docker 容器使用. 兼容性: .NET Core 通过 .NET Standard与 .NET Framework.Xamarin 和 Mon

不装mono,你的.NET程序照样可以在Linux上运行!

让.NET应用程序在linux上运行,目前通用的做法就是在Linux上安装mono,然后通过”mono your.exe“命令运行这个程序. 这种运行.net程序的办法有两个弱点,一个是需要客户机安装mono,二个是 ”mono xx.exe“ 这种命令行总让人感到有点不太专业的味道. 那么,有没有办法既不安装mono,又能让你的.NET程序就像c语言编译的程序那么,”直接“在Linux上运行呢? 为了解决.net程序特别是.net控制台程序在Linux平台上部署和运行的简便性问题,我给大家准备

Android应用程序绑定服务(bindService)的过程源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6745181 Android应用程序组件Service与Activity一样,既可以在新的进程中启动,也可以在应用程序进程内部 启动:前面我们已经分析了在新的进程中启动Service的过程,本文将要介绍在应用程序内部绑定Service的过程,这是一种在应用程序进程内部启动 Service的方法. 在前面一篇文章Android进程间通信(IPC)机

VS2015升级Update2之后Cordova程序提示:此应用程序无法在此电脑上运行

VS2015在升级到Update2之后,有可能出现如下异常,在运行Cordova项目时提示: 查看输出面板会有乱码错误信息: 出现此问题的原因是在于npm程序损坏了.vs调用的npm程序并不是在node安装目录下的npm,而是在: C:\Users\用户名\AppData\Roaming\Microsoft\VisualStudio\MDA\vs-npm\版本号\ 升级完VS之后会发现npm.cmd已经无法启动运行了.所以Cordova项目在运行时,无法使用npm来解析包导致程序无法运行. 解决

限制TensorFlow只在CPU上运行的方法

我们知道安装tensorflow可以安装两个版本的,有cpu和gpu的,如果同时安装的时候会默认进行用gpu进行,那如歌更改为使用cpu呢 在import tensorflow/keras 之前,加入如下代码 import os os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" os.environ["CUDA_VISIBLE_DEVICES"] = "-1" 原文地址:https:

呃,如何使 .NET 程序,在 64位 系统 中,以 32位 模式运行。

其实最简单的方法就是在解决方案中,把平台设为 x86 就好了哈~   但是今天遇到一个第三方的软件,它调用的一个 dll 是 32位 的,可能它没有测试过在 64位 系统下运行的情况,它在编译时是按默认的配置设为了 Any CPU. 但是在 64位 系统中以默认 64位 模式运行时,再调用 32位 的 dll 就报异常了... 于是想到的一个办法就是让它在 64位 系统中,以 32位 模式运行就好了~   在网上搜了一下,没想到 .NET 本身就提供了这个修改工具,叫做 CorFlags.exe

linux下将不同线程绑定到不同core和cpu上——pthread_setaffinity_np

=============================================================== linux下的单进程多线程的程序,要实现每个线程平均分配到多核cpu,主要有2个方法 1:利用linux系统自己的线程切换机制,linux有一个服务叫做irqbalance,这个服务是linux系统自带的,默认会启动,这个服务的作用就是把多线程平均分配到CPU的每个核上面,只要这个服务不停止,多线程分配就可以自己实现.但是要注意,如果线程函数内部的有某个循环,且该循环内