签到

05月06日
尚未签到

共有回帖数 0

    幻梦如戏

    等级:
    那么开始了!

    1.80X86 32位CPU的编程模型programming model

    80X86有16个通用寄存器register。从某种程度上来说介绍80X86的CPU编程模型,就是介绍这16个寄存器。没听过CPU寄存器这名字的童鞋不用看下去了请回家睡觉去。本文基本上适合大抵知道汇编怎么回事的童鞋而不是完全的汇编白痴。
    另外,介绍的是32位汇编,请把80X86的16位汇编先忘记掉,这么短的文章不可能介绍完汇编,而是给出汇编最基本的东西和最简单的抽象。可能的话稍微解释下16位汇编。

    eax ebx ecx edx
    这4个寄存器是通用寄存器。用来暂存数据的地方。

    esi edi(extension source index, extension destination index.)
    它们也可以用来暂存数据。更一般的是伴随串指令使用。

    esp ebp(extension stack pointer, extension base pointer)
    栈指针寄存器和基址指针寄存器。关于栈和过程调用,最重要的寄存器就是这两个了!绝对不要忘掉这两个!

    eip(extension instruction pointer, or program count)
    指令指针寄存器!这就是“顺序存储控制”的核心!又称程序计数器!

    eflag
    标志寄存器。算术、逻辑及相关指令运算会影响该标志寄存器中的位。这个寄存器很重要也很麻烦。

    以上!就是32位汇编(又称平坦地址模式汇编)会使用到的所有寄存器,一共十个,都是32位的。啊不是说十六个寄存器吗?

    对,还有6个寄存器,分别名为:
    cs ;代码段寄存器code segment
    ds ;数据段寄存器data segment
    ss ;栈寄存器stack segment
    es fs gs; 附加段寄存器
    这6个寄存器都是16位寄存器。即使是现今的80686 32位系统中,它们仍然是16位的。这些段寄存器在8086中用来对内存地址进行段指定。有8086 16位汇编知识的同学都知道怎么回事,...还是解释一下吧,8086是16位CPU,而地址线是20位。20根地址线表明能寻址的空间是2^20也就是1M(1024 * 1024).16位不够表达1M的地址空间,因此由“段*16+偏移”得到内存地址值。
    但是在32位系统中,这些段寄存器已经不怎么使用了。总之32位汇编不需要关注这些寄存器,因为32位系统CPU和各寄存器是32位,地址线也是32位,一个32位值足够表达32位寻址空间。...实际上这些段寄存器在32位系统中是同一个值,用来指向某个索引表,但这是本文不需要在此关注的东西。

    以上,16个寄存器介绍完毕!接下来介绍简洁的编程模型抽象!
    由于是简单而本质的抽象,因此我们不考虑分页机制、MMU(memory management unit)之类的。正是如此,它们本来对于我们就是透明的。

    所以内存就被考虑为一个从编号(地址)0开始、以编号(地址)0xffff ffff结束的字节序列。每一个字节都被顺序地编号。编号就是字节的地址。
    在32位FLAT模式汇编中,本来就是如此。

    在程序加载入内存后,程序的指令和数据都按某种方式存放在内存里面。要访问和执行他们,只需要知道他们的地址就可以了。

    最重要的东西登场,它就是eip,指令指针寄存器,或称程序计数器。eip中的值程序员无法修改(嗯,可是汇编程序员呢?汇编程序员也无法修改它的值吗?废话,汇编程序员也是程序员啊!),它的值就是下一条即将执行的指令的地址。就是说eip永远指向下一条指令。

    然后就是esp,它指向栈的栈顶。当向栈压入数据或从栈弹出数据时,esp的值不断变化,但无论如何变化,它都指向栈顶。

    最后就是ebp,它用来把栈中的某个地址作为基址(基本地址,这样理解就是了),它用来标识栈中的某个固定位置,因此可以通过它访问这个固定位置附近的数据。

    80X86的栈是向下增长的。也就是说,当向栈压入4个字节的数据时,esp = esp - 4; 当从栈中弹出4个字节时,esp = esp + 4。

    以上!多么幸福的事情啊,32位汇编只需要在意这3个寄存器就可以了!(标志寄存器也挺重要的啊!但是跟本文要陈述的东西没太大关系,略)。


    这你妹,解释下上图代表什么意思...纯粹照顾完全的新手。
    首先,那一排格子代表内存空间中的一小段,每个格子代表4个字节。右边的十六位数值代表方格的地址。格子中间的“...”代表格子的内容。
    图中地址是从下往上增长的。
    esp永远指向栈顶。一开始它指向地址为0x0063 fff4的字节。然后向栈压入4个字节。
    对80X86来说,指令就是push ...;
    数据压入后,esp指向0x0063 fff0。这是新的栈顶。

    弹出数据跟上面的过程相反。esp中的值会增加。

    ...这你妹,我画这干嘛,多此一举...好像画了图也没能说的更明白或者表达更深层次的意思啊。总之就是这样了,esp永远指向栈顶,记住就OK。
    关于80X86 32位CPU汇编模型就讲上面这些了。之所以讲这么少,因为这就是最基本的和最本质的内容,讲多了反而把重点搞没了。
    总结就是记住3个寄存器。eip, esp, ebp。记住他们的意义就可以了。

    2.栈帧与C函数调用
    关于这个其实没有什么好讲的。

    关于计算机,最重要的三个抽象是什么?答案是虚拟地址空间、进程、文件。

    一个进程就是一个运行中的程序,或者被加载到内存中的程序。现代操作系统使进程看上去独占了所有的系统资源,但实际上系统中运行着多个进程。

    所以从一个进程的视角看去,它独占了系统中的所有内存资源和CPU资源。对于32位系统虚拟地址空间被抽象为编号0~0xffff ffff的字节序列,它是平坦的,线性的,被系统抽象了的,所以叫它平坦地址或线性地址、虚拟地址。

    对于Linux来说,保留高1G为系统使用。0-3G空间被应用程序也就是进程独占。

    对于一个被加载了的程序也就是进程,其在内存中的分布为:

    共享内存段
    自由存储区(堆)
    BSS段
    数据段
    只读数据段                        
    代码段                            

    栈向下增长。

    每一个函数调用,都是一个栈帧。
    以下代码:
    int add(int x, int y)
    {
       int z;
       z = x + y;
       return z;
    }
    int main(int argc, char* argv[])
    {
       add(3, 5);
       return 0;
    }
    那么main函数是一个栈帧,add是一个栈帧。
    当程序运行时,main函数栈帧先被建立,这个栈帧在高地址。然后调用add函数。此时add函数栈帧被建立,在低地址。当程序执行流进入add函数时,add函数内的局部变量在add函数栈帧中被建立。然后add返回。当add函数返回,此时add函数栈帧被销毁,同时add函数内的局部变量也被销毁。所以,C编程原则告诉我们:永远不要返回一个指向局部对象的指针。也就是说如下代码是错误的:
    int* getNumber(void)
    {
       int a = 3;
       return &a;
    }

    那么运行时的栈是什么样子的呢?它是一个随着运行,不断增长(进入新的函数调用)和缩短(函数返回)的动态影像。

    OK,关于C栈帧就说到这里,完毕。

    楼主 2015-11-26 15:10 回复

共有回帖数 0
  • 回 帖
  • 表情 图片 视频
  • 发表

登录直线网账号

Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号 意见反馈 | 关于直线 | 版权声明 | 会员须知