1.gdb常用调试命令

要用gdb调试的话,编译命令需要添加-g参数,例如

[plain] view plaincopy

  1. gcc -g main.c -o main

b linenum          在第 linenum行打断点

l                           显示源代码;

Ctrl-d                  退出gdb

where                 显示当前程序运行位置

print  /d $eax    十进制地方式打印$eax 值,/x是十六进制,/t是二进制

c                          执行到下一个断点

n                          下一行

layout split          把当前Terminal分割成两半,上面显示源码及汇编,下面可以输入调试命令,效果如下:

2.Example.c程序分析

程序代码:

[cpp] view plaincopy

  1. #include <stdio.h>
  2. intg(intx)
  3. {
  4. returnx+3;
  5. }
  6. intf(intx)
  7. {
  8. returng(x);
  9. }
  10. intmain(void)
  11. {
  12. printf("Hello\n");
  13. returnf(8)+1;
  14. }




将源代码编译为二进制文件又需要经过以下四个步骤:预处理(cpp) → 编译(gcc或g++) → 汇编(as) → 链接(ld) ;括号中表示每个阶段所使用的程序,它们分别属于 GCC 和 Binutils 软件包。

用gcc的编译参数和生成的对应文件。

2.1预编译

[plain] view plaincopy

  1. gcc -E Example.c -o Example.cpp



生成的cpp文件内容如下:

[cpp] view plaincopy

  1. ...
  2. ...
  3. ...
  4. //a lot of extern statement
  5. externchar*ctermid (char*__s) __attribute__ ((__nothrow__ , __leaf__));
  6. # 910 "/usr/include/stdio.h" 3 4
  7. externvoidflockfile (FILE*__stream) __attribute__ ((__nothrow__ , __leaf__));
  8. externintftrylockfile (FILE*__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
  9. externvoidfunlockfile (FILE*__stream) __attribute__ ((__nothrow__ , __leaf__));
  10. # 940 "/usr/include/stdio.h" 3 4
  11. # 2 "Example.c" 2
  12. intg(intx)
  13. {
  14. returnx+3;
  15. }
  16. intf(intx)
  17. {
  18. returng(x);
  19. }
  20. intmain(void)
  21. {
  22. returnf(8)+1;
  23. }





主要代码基本没有变化,添加了很多extern声明。



分析

预编译的主要作用如下:
●将源文件中以”include”格式包含的文件复制到编译的源文件中。
●用实际值替换用“#define”定义的字符串。
●根据“#if”后面的条件决定需要编译的代码。

在该阶段,编译器将C源代码中的包含的头文件stdio.h编译进来,生成扩展的c程序。当对一个源文件进行编译时, 系统将自动引用预处理程序对源程序中的预处理部分作处理, 处理完毕自动进入对源程序的编译。

2.2编译

执行编译的结果是得到汇编代码。

[sql] view plaincopy

  1. gcc -S Example.c -o Example.s



生成.s文件内容如下:

[plain] view plaincopy

  1. .file   "Example.c"
  2. .text
  3. .globl  g
  4. .type   g, @function
  5. g:
  6. .LFB0:
  7. .cfi_startproc
  8. pushl   %ebp               ;ebp寄存器内容压栈
  9. .cfi_def_cfa_offset 8
  10. .cfi_offset 5, -8
  11. movl    %esp, %ebp         ;esp值赋给ebp,设置函数的栈基址。
  12. .cfi_def_cfa_register 5
  13. movl    8(%ebp), %eax      ;将ebp+8所指向内存的内容存至eax
  14. addl    $3, %eax           ;将3与eax中的数值相加,结果存至eax中
  15. popl    %ebp               ;ebp中的内容出栈
  16. .cfi_restore 5
  17. .cfi_def_cfa 4, 4
  18. ret
  19. .cfi_endproc
  20. .LFE0:
  21. .size   g, .-g
  22. .globl  f
  23. .type   f, @function
  24. f:
  25. .LFB1:
  26. .cfi_startproc
  27. pushl   %ebp              ;ebp寄存器内容压栈
  28. .cfi_def_cfa_offset 8
  29. .cfi_offset 5, -8
  30. movl    %esp, %ebp        ;esp值赋给ebp,设置函数的栈基址。
  31. .cfi_def_cfa_register 5
  32. subl    $4, %esp          ;esp下移动四个单位
  33. movl    8(%ebp), %eax     ;将ebp+8所指向内存的内容存至eax
  34. movl    %eax, (%esp)      ;将eax存至esp所指内存中
  35. call    g                 ;调用g函数
  36. leave                     ;将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址
  37. .cfi_restore 5
  38. .cfi_def_cfa 4, 4
  39. ret                              ;函数返回,回到上级调用
  40. .cfi_endproc
  41. .LFE1:
  42. .size   f, .-f
  43. .globl  main
  44. .type   main, @function
  45. main:
  46. .LFB2:
  47. .cfi_startproc
  48. pushl   %ebp               ;ebp寄存器内容压栈
  49. .cfi_def_cfa_offset 8
  50. .cfi_offset 5, -8
  51. movl    %esp, %ebp         ;esp值赋给ebp,设置函数的栈基址。
  52. .cfi_def_cfa_register 5
  53. subl    $4, %esp           ;esp下移动四个单位
  54. movl    $8, (%esp)         ;将8存入esp所指向的内存空间
  55. call    f                  ;调用f函数
  56. addl    $1, %eax           ;将1与eax的内容相加
  57. leave                      ;将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址
  58. .cfi_restore 5
  59. .cfi_def_cfa 4, 4
  60. ret                        ;函数返回,回到上级调用
  61. .cfi_endproc
  62. .LFE2:
  63. .size   main, .-main
  64. .ident  "GCC: (SUSE Linux) 4.7.1 20120723 [gcc-4_7-branch revision 189773]"
  65. .section    .comment.SUSE.OPTs,"MS",@progbits,1
  66. .string "ospwg"
  67. .section    .note.GNU-stack,"",@progbits



分析

第1行为gcc留下的文件信息;第2行标识下面一段是代码段,第3、4行表示这是g函数的入口,第5行为入口标号;6~20行为 g 函数体,稍后 分析;21行为 f 函数的代码段的大小;22、23行表示这是 f 函数的入口;24行为入口标识,25到41为 f 函数的汇编实现;42行为f函数的代码段的大小;43、44行表示这是main函数的入口;45行为入口标识,46到62为main函数的汇编实现;63行为main函数的代码段的大小;54到67行为 gcc留下的信息。

具体程序运行时内存的调用情况如下图:

以.cfi开头的命令如.cfi_startproc,主要用于作用是出现异常时stack的回滚(unwind),而回滚的过程是一级级CFA往上回退,直到异常被catch。

这里不做讨论,需要详细了解的点这里。

每一个函数在开始都会调用到

[plain] view plaincopy

  1. pushl %ebp      ;ebp寄存器内容压栈,即保存函数的上级调用函数的栈基地址
  2. movl %esp,%ebp  ;esp值赋给ebp,设置函数的栈基址



主要作用是保存当前程序执行的状态。

还有两句在函数调用结束时也会出现:

[plain] view plaincopy

  1. leave ; 将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址
  2. ret ; 函数返回,回到上级调用



用于在函数执行完后回到执行前的状态。

还有要注意的是汇编中的push和pop

pop系列指令的格式是:
pop destination
pop指令把栈顶指定长度的数据存放到destination中,并且设置相应的esp的值使它始终指向栈顶位置。

push刚好相反。

pushl %eax 等价于

subl $4 %esp

movl %eax (%esp)

popl %eax 等价于

movl (%esp) %eax

addl %4 %esp

2.3汇编

汇编之后得到的是.o文件,终端执行命令:

[plain] view plaincopy

  1. as Example.s -o Example.o



在终端用vim打开:

[plain] view plaincopy

  1. vim -b Example.o



用16进制进行查看,在vim中输入

[plain] view plaincopy

  1. :%!xxd



结果如下(未完全显示)

分析

目标文件就是源代码编译后但未进行链接的那些中间文件,包含有编译后的机器指令代码,还包括链接时所需要的一些信息,比如符号表、调试信息、字符串等。

可以查看目标文件的信息,在终端执行

[plain] view plaincopy

  1. file Example.o



得到:

Example.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

其中的relocatable指出该文件为ELF中的可重定位文件类型。

2.4链接

链接后的文件为可执行文件,在linux中没有扩展名。

终端执行:

[plain] view plaincopy

  1. gcc Example.o -o Example



执行Example,终端运行:

[plain] view plaincopy

  1. ./Example



运行结果:

分析

用file命令查看Example属性:

[plain] view plaincopy

  1. file Example



Example: ELF 32-bit LSB executable , Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.16, BuildID[sha1]=0xffdc8de348d59ce38f1f933e55b7a5c55184ef39, not stripped


其中的executable指出该文件为ELF中的可执行文件类型。

由于程序没有任何打印语句,所以程序执行完之后就直接退出了。

3.计算机工作流程-单任务和多任务

暂且讨论最简单的计算机,只包含CPU,存储器,I/O控制芯片
如果一个用户在同一时间只能运行一个应用程序,则对应的操作系统称为单任务操作系统,如MS-DOS。
如果用户在同一时间可以运行多个应用程序(每个应用程序被称作一个任务),则这样的操作系统被称为多任务操作系统,如windows 7,Mac OS 。
在最早期的单任务计算机中,用户一次只能运行一个程序,计算机首先从外存中加载程序到内存,然后依次执行程序指令,完全执行完毕之后才可以加载、执行下一个程序。
由于当时CPU的资源十分珍贵,为了充分利用,在这之后出现了多道程序,当某个程序暂时无需使用CPU时,监控程序就把另外的正在等待CPU资源的程序启动,使得CPU充分利用。缺点是程序的运行没有优先级。
在这之后又出现了分时系统,程序运行模式变成了一种协作的模式,即每个程序运行一段时间以后都主动让出CPU。
分时系统继续发展就到了今天的多任务系统 - 所有的程序都以进程的方式运行在比操作系统权限更低的级别,每个进程都有自己的独立空间,CPU由操作系统统一进行分配,每个进程根据进程优先级的高低都有获得CPU的机会。
多任务的实现主要依靠MMU(Memory Management Unit:内存管理单元)。
MMU的主要工作就是将程序的虚拟地址(编译器和链接器计算的)转换成内存的物理地址(硬件电路决定的)。
MMU可以通过重定位任务地址而不需要移动在内存中的任务。任务的物理内存只是简单的通过激活与不激活页表来实现映射到虚拟内存。
本文转载自:http://blog.csdn.net/qp120291570/article/details/8913206

How program works相关推荐

  1. 开源(Open Source)那些事儿 (一)

    景 最近有幸参与了王克伟的开源项目iToday,详情可以参考 我在Windows嵌入式系统上的一个绚丽用户界面开源项目(iToday).克伟的号召力超人,Q群一下子就爆满200人.如果扩容了,大家有兴 ...

  2. 要求用户提供输入,直到他们给出有效的答复

    本文翻译自:Asking the user for input until they give a valid response I am writing a program that accepts ...

  3. MSIL 教程(二):数组、分支、循环、使用不安全代码和如何调用Win32 API(转)...

    转自:http://www.cnblogs.com/Yahong111/archive/2007/08/16/857574.html 续上文[翻译]MSIL 教程(一) ,本文继续讲解数组.分支.循环 ...

  4. ssh tunnel 上网

    用DNS隧道实现免费上网 大多数机场.酒店之类场所,当你输入一个网址比如www.google.com时,会弹出一个页面要你输入帐号密码才能上网.这个时候DNS能正确解析,但是上网要付费认证. 可以通过 ...

  5. winpcap编程 解析数据包

    WinPcap和Libpcap的最强大的特性之一,是拥有过滤数据包的引擎. 它提供了有效的方法去获取网络中的某些数据包,这也是WinPcap捕获机制中的一个组成部分. 用来过滤数据包的函数是 pcap ...

  6. 软件分类:自由软件、开放源代码软件、公共软件、私有软件、版权所无软件...

    自由软件(free software) "Free software" means software that respects users' freedom and commun ...

  7. qpython怎么用matplotlib_python-通过文本框的交互式matplotlib图

    我正在尝试创建具有三个参数变化的多维函数的交互式matplotlib图.问题在于参数可以在很大的范围内变化,因此我宁愿不使用滑块,而直接键入想要的值.基本上,我想重新创建下面的规范示例,在该示例中,我 ...

  8. ui设计未来十年前景_UI设计的10条诫命

    ui设计未来十年前景 重点 (Top highlight) The year is approximately 1,300 BC when Moses received the 10 UI desig ...

  9. php字符串反转函数_PHP | 反转给定的字符串而不使用库函数

    php字符串反转函数 Given a string and we have to reverse it without using a library function. 给定一个字符串,我们必须不使 ...

最新文章

  1. 程序猿的节日:1024,今天祝愿全球所有程序猿们、IT精英们节日快乐!——我在上海写代码
  2. 花椒web端实时互动流媒体播放器
  3. SAP IBASE category 01 download
  4. mybatis学习(23):分页1 多参数传递(索引方式)
  5. IDEA中 @override报错的解决方法
  6. windows上的一些命令和工具
  7. Git笔记(27) 储藏与清理
  8. 报表中表达式的全局集合(Visual Studio 报表设计器)
  9. python官网的软件-python
  10. 编程基本功:给不同的电脑贴标
  11. (LINPACK)HPL测试成功步骤整理
  12. mysql加载audit失败_MySQL5.5 安装mcafee mysql-audit插件 不成功
  13. 微信内嵌浏览器打开手机浏览器下载APP(APK)的方法
  14. 2022年下半年软考报名时间陆续公布(持续更新)
  15. 基于JavaScript+css写一个简单的h5动态下雨效果
  16. Java GridBagLayout(网格包布局管理器)
  17. mysql主从同步的三种模式
  18. 移位运算符(<<、>>和>>>)
  19. vue 安装不上,报错,解决办法如下
  20. 大疆Tello编队飞行教程(特洛教育版)/多机视频流获取

热门文章

  1. Linux的ELF格式分析
  2. 用Perl发送邮件小例子
  3. git add --all 为啥不能添加空文件夹,这样设计的初衷是
  4. BEX5下新建任务到待办任务
  5. Cisco访问控制列表配置指南
  6. ['1', '2', '3'].map(parseInt) what why ?
  7. Linux安装与硬盘分区
  8. Residual Networks
  9. C/C++中static关键字详解
  10. 下载android的linux内核的方法