Linux内核课第一周作业。本文在ubuntu14的环境下进行实验

burningcodes 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1.AT&T与Intel汇编

linux下gcc采用的是AT&T的汇编格式,而windows下都是采用Intel汇编格式,他们之间的差别主要如下:

  • 寄存器命名: AT&T中寄存器名字前带%,如%eax,而intel不带%,如eax
  • 源,目的操作数顺序 : AT&T中源在前,目的在后,如  mov src,dst  ; intel则相反
  • 操作数长度 : AT&T中每个操作都有一个长度后缀,如movb (字节),movl(双字),movw(字),而intel则没有这种后缀
  • 立即数格式: AT&T中立即数前有$,如$0x01;intel则没有$,如0x01
  • 间接寻址的语法 : INTEL 中基地址使用“[” 、“]” ,而在 AT&T 中使用“(”、“)”

2.栈的妙用

对于一个进程而言,它的虚拟地址空间(32位)结构大致如下:

addr

在虚拟地址空间中有一段空间比较特殊 — 栈。其他部分都是向高地址方向增长,唯独栈是以相反的方式增长。栈在计算机中最常见的用处就是函数调用了,比如下面这段代码:

main中调用了f,f中调用了g,函数返回时为什么能返回到正确的位置?这个过程中用栈来保存参数及返回地址,函数返回时再从栈中取出保存的地址,这样就不影响主程序的继续执行。函数栈帧的基本结构如下:

zhan

在32位程序中由ebp和esp两个寄存器来确定当前函数的栈帧范围(64位程序中已经没有了栈基址的概念),ebp所指向的栈空间中存着上一个栈帧的栈基址,所以根据ebp可以回溯所有的栈基址。esp始终指向栈顶,一般情况下,通过ebp来存取函数参数。

3.C程序的背后发生了什么?

用gcc编译上面那段代码,并且用objdump查看输出的可执行程序:

disas

其中,一些主要汇编指令的等价指令如下:

ins

从上面的汇编代码可以看出,进入函数有固定的两句话:

这两句的作用是保存上一个函数的栈基址,与这两句话相对应的是leave指令(函数g中没有leave是因为esp寄存器没有变化所以被编译器优化了)。函数栈帧图如下:

zhan2

函数f中接下来是一条sub指令:

这句话为当前函数分配新的栈空间,这里为该函数分配了4字节的空间。然后就是取出函数参数:

ebp+4的位置存着返回地址eip,从ebp+8开始存着函数的参数,一般情况下是从右到做依次入栈 。在汇编中源操作数和目的操作数不能同时为存储器地址,所以有的时候要使用寄存器临时存放数据。call指令又调用了新的函数,最后的leave和ret完成清理栈帧并且返回的工作。

4.真的是main吗?

学过C语言的都知道,任何c程序都得有个main函数,main函数是程序执行时“第一个”被调用的函数, 实际情况真的如此吗?让我们继续看看上面那个可执行程序:

elf

通过readelf可以看到,可执行程序的入口地址是0x80482f0,那这个地址中有是什么呢?是不是main函数呢:

start

这个地址是代码段的起始位置,存的是_start这个函数,并且这个函数里面还是没有调用main,而是调用了__libc_start_main这个函数,那这个函数又在哪里呢?接下来用nm命令看看这个符号(注:nm命令用来查看目标文件中的各个符号)

main

其中U表示未定为,说明__libc_start_main这个函数并不在这个可执行模块中,而是动态链接进来的,它位于GLIBC_2.0这个库中。而main函数又是在__libc_start_main中被显示调用的,所以c程序都得写一个main函数,这个main函数也就是用户自定义代码的入口。

5.总结

在冯诺依曼体系的计算机中,存储在物理存储器上的代码和数据没有任何区别,cpu总是不断的进行“取指令,执行指令”的过程,所以要告诉cpu什么时候取到的是代码,什么时候取到的是数据。从cpu指令集到汇编再到各种高级语言这一切都归结于0和1的组合,只有理解了计算机底层的原理才不会被各种高级语言“蒙蔽”双眼,才能做到“游刃有余”。

 

观看更多有关 的文章?

*

+
跳转到评论