《Linux内核分析》之计算机是如何工作的 实验总结

前言

前两天在家电脑win7系统一直打不开网站,今天换了个网反而好了,具体原因未知。

马马虎虎学完了Python课程,一直想学下linux,看到里面有个linux的就选上了。当初没细看,如今听完第一节课有点傻眼,竟然糊里糊涂给自己找了一科汇编语言的课程,静心看下去庆幸自己还知道点堆栈的知识并出现了轻微的自虐倾向。闲话少说,现开正题。注:本文具有总结兼作业性质,如有雷同,纯属巧合。

实验及总结

本实现代码及汇编部分均在实验楼完成。

实验代码

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

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

int main(void)
{
      return f(8) + 1;
}

 汇编后代码及部分代码截图

使用gcc –S –o main.s main.c -m32命令编译成汇编代码

代码

    .file    "main.c"
    .text
    .globl    g
    .type    g, @function
g:
.LFB0:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    movl    8(%ebp), %eax
    addl    $2, %eax
    popl    %ebp
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size    g, .-g
    .globl    f
    .type    f, @function
f:
.LFB1:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $4, %esp
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    g
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1:
    .size    f, .-f
    .globl    main
    .type    main, @function
main:
.LFB2:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $4, %esp
    movl    $8, (%esp)
    call    f
    addl    $1, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE2:
    .size    main, .-main
    .ident    "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

截图

纯净的汇编代码

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

汇编代码分析

堆栈信息变更参考图

和一般代码执行一样从main函数开始。

esp堆栈的栈顶

ebp堆栈的基址(栈底)

eip当前的指令指针,eip寄存器不能被直接修改,只能通过特殊指令间接修改,故call/ret 例子中eip(*)表示伪指令

eax 函数的返回值默认使用eax寄存器来返回给上级函数。

假设一开始为空栈,初始位置为0,ebp=esp,如图:

  1. 将当前ebp压栈同时esp的值被修改,即esp指向位置(1)
  1. ebp指向esp的位置,即ebp指向位置(1)
  1. esp-4即将esp向下移动一个编号,即esp指向位置(2)
  1. 将8放到当前esp的位置,即将8放在位置(2)
  1. 调用f函数 call f 对应的指令:push1 %eip(*) 和movl f,%eip(*)

此时eip实际指向下一行即第23行代码,将eip(标号23)存入位置(3)

同时esp指向位置(3),eip指向f

  1. 将ebp的地址(位置1)存入位置(4),esp指向位置(4),此时代码进入f函数(模块)并执行其第一语句
  2. 使ebp指向与esp相同的地址,即位置4
  3. esp指向位置(5)
  4. 将ebp+8变址寻址,即向上2个标号的位置,由于该位置的值为8,即eax=8
  5. 将eax的值放到esp的位置,即将8放到位置(5)
  6. 调用g函数 call g对应的指令:push1 %eip(*) 和movl g,%eip(*) esp移到位置(6),此时eip实际指向下一行即第15行代码,将eip(15)存入位置(6),同时eip指向g
  7. 将ebp值ebp(4)存入位置(7),esp指向位置(7),此时代码进入g函数(模块)并执行其第一语句
  8. 使ebp指向与esp相同的地址,即位置7
  9. 将ebp+8变址寻址,即向上2个标号的位置,由于该位置的值为8,即eax=8
  10. 将2加到eax中,即2+8=10,此时eax=10
  11. ebp指向位置(4),esp指向位置(6)
  12. esp指向位置(5),同时eip重新指向行号15
  13. esp指向ebp的地址(位置4),即esp指向位置(4)
  14. ebp指向位置(1),esp指向位置(3)
  15. eip重新指向行号23
  16. eax的值加上1,eax=11
  17. esp指向ebp的地址(位置1),即esp指向位置(1)
  18. ebp指向位置(0),esp指向位置(0)
  19. main函数返回,eip重新指向该main函数调用前中断的地址,运行其他指令

计算机是如何工作的

采用冯·诺依曼体系结构,使用存储程序方式,cpu和内存用总线连接。

从硬件角度

cpu中含有寄存器,其中寄存器ip总是指向内存的某一区域(内存cs即代码段)

cup从ip指向的内存地址取出一条指令执行,执行完后ip自加1,取下一条指令再执行,如此循环。

程序员的角度

CPU为for循环,总是从内存中取出指令解释和执行

内存保存指令和数据

CPU负责解释和执行这些指令

cpu和内存之间用总线连接

附录

mov寻址指令

b,w,l,q分别代表8位、16位、32位和64位。此处以32为例

cup对内存的操作方法

mov指令

常用mov寻址命令

movl %eax,%edx edx =eax;

解释:register mode 寄存器寻址

本句含义:将eax寄存器里的内容放到edx寄存器中,相当于后面的edx =eax

movl $0x123,%edx edx = 0x123;

解释: immediate  立即寻址

$+16进制的数字即:立即数是以¥开头的数值。和内存无关

本句含义:将数值直接放在edx中,相当于后面的edx = 0x123

movl 0x123,%edx edx=*(int32_t*)0x123;

解释: direct 直接寻址

直接访问一个指定的内存地址的数据。无$,表一个地址

本句含义:将内存地址16进制的123所指向的内存数据放到edx中,相当于后面 edx=*(int32_t*)0x123即c语言中将数值强制转换为32位int变量的指针,在用一个*的指针取其指向的值

movl (%ebx),%edx edx =*(int32_t*)ebx;

解释:indirect 间接寻址

将寄存器的值作为一个内存地址来访问内存

本句含义:寄存器%ebx加()表ebx存的值(是个内存地址),加括号表示将内存地址所存储的数据放到edx中,即将ebx强制转换为一个地址,再加*取它的值。

movl 4(%ebx),%edx edx=*(int32_t*)(ebx+4);

解释:displaced  变址寻址

在间接寻址之时改变寄存器的数值

本句含义:在间接寻址的基础上,先给地址+个立即数,然后转换成int类型的指针,再取值

常用汇编指令简记

常用汇编指令

push1 %eax  

将 寄存器%eax压栈到栈顶,栈的位置在增长

what it does

subl $4,%esp   压栈 esp-4,将堆栈的栈顶

movl %eax,(%esp) 间接寻址将eax放置到esp处

提示:push1 %ebp 将当前ebp压栈同时esp的值被修改


popl %eax 

出栈,栈的位置在收缩

what it does

movl (%esp),%eax 将栈顶的数值放在eax里

addl $4,%esp            将esp+4相当于回退一个位置


call 0x12345

函数调用地址(函数调用堆栈)

理解C代码在CPU上执行的关键

what it does

push1 %eip(*) 对当前指令压栈

movl $0x12345,%eip(*) 使下一条指令执行位置为0x12345


ret

与call对应,将call时保存的eip还原到eip寄存器中即执行call之前的下条指令

what it does

popl %eip(*)


leave

撤销堆栈

what it does

movl %ebp,%esp

popl %ebp


enter

将栈置为一个新的空栈

what it does

pushl %ebp

movl %esp,%ebp

小知识点

必知小知识点

1、栈是向下增长的,向2000H的地址存入数据实际存储数据的是2000H-2003H这四个字节,也就是指针指向的是单元格的下边界来表示指向一个单元格。

2、对x86计算机大多数的指令可直接访问一个内存地址

AT&T汇编格式与Intel汇编格式略有不同

Linux内核使用的是AT&T汇编格式

windCoder原创作品转载请注明出处

参考资料

本文部分资料与图片参考自

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木宛城主

Unity应用架构设计(7)——IoC工厂理念先行

一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂。IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常...

2877
来自专栏C/C++基础

Linux命令(11)——col命令

col命令是一个标准输入文本过滤器,它从标准输入读取内容,输出到标准输出。在许多UNIX说明文件里,包含控制字符。当我们运用Shell特殊字符>和>>,把说明文...

1032
来自专栏李航的专栏

Shell 主要逻辑源码级分析:SHELL 运行流程 (1)

分享一下在学校的时候分析shell源码的一些收获,帮助大家了解shell的一个工作流程,从软件设计的角度,看看shell这样一个历史悠久的软件的一些设计优点和缺...

2.1K0
来自专栏Java技术分享圈

杨老师课堂之JavaSe 部分面试题

​ JVM 是 JavaVirtual Machine 的缩写,全称是 Java 虚拟机。Java 语言的一个非常重要的 特性就是跨平台性,而 Java 虚...

943
来自专栏枕边书

搭建自己的PHP框架心得(二)

续言 对于本次更新,我想说: 本框架由本人挑时间完善,而我还不是PHP大神级的人物,所以框架漏洞难免,求大神们指出。 本框架的知识点应用都会写在博客里,大家有什...

2618
来自专栏专注 Java 基础分享

虚拟机类加载机制

虚拟机把字节码文件从磁盘加载进内存的这个过程,我们可以粗糙的称之为「类加载」,因为「类加载」不仅仅是读取一段字节码文件那么简单,虚拟机还要进行必要的「验证」、「...

4707
来自专栏眯眯眼猫头鹰的小树杈

猫头鹰的深夜翻译:理解java的classloader

Java ClassLoader是java运行系统中一个至关重要但是经常被忽略的组件。它负责在运行时寻找并加载类文件。创建自定义的ClassLoader可以彻底...

1434
来自专栏服务端技术杂谈

Golang笔记

静态编译 编译时一个将源代码翻译成低级语言的过程。编译过程比较慢,在设计Go时,编译速度是主要的设计目标之一。静态类型意味着变量必须指定一个类型,如整形,字符串...

3014
来自专栏张善友的专栏

通用的序列号生成器库

正如文章《通用的业务编号规则设计实现(附源码)》 文章里需要一个多实例和线程安全的序列化生成器,在SQL Server 2012+ 版本 有一个通过.NET程序...

1985
来自专栏皮皮之路

【JVM】浅谈双亲委派和破坏双亲委派

笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践...

2622

扫码关注云+社区

领取腾讯云代金券