公版ubuntu自带memtest86+内存测试工具,出于工作需要,分析了其工作流程记录于此。

分析一个陌生的程序,当然得先找入口入口函数,很可惜main()/_start之类的都找到,唯一看着像入口点的main.c文件也没找到可能的入口点。看来只能从makefile文件分析了。

OBJS= head.o reloc.o main.o test.o init.o lib.o patn.o screen_buffer.o \config.o linuxbios.o memsize.o pci.o controller.o random.o spd.o \error.o dmi.o cpuid.oall: memtest.bin memtest# Link it statically once so I know I don't have undefined
# symbols and then link it dynamically so I have full
# relocation information
memtest_shared: $(OBJS) memtest_shared.lds Makefile$(LD) --warn-constructors --warn-common -static -T memtest_shared.lds \-o $@ $(OBJS) && \$(LD) -shared -Bsymbolic -T memtest_shared.lds -o $@ $(OBJS)memtest_shared.bin: memtest_sharedobjcopy -O binary $< memtest_shared.binmemtest: memtest_shared.bin memtest.lds$(LD) -s -T memtest.lds -b binary memtest_shared.bin -o $@head.s: head.S config.h defs.h test.h$(CC) -E -traditional $< -o $@bootsect.s: bootsect.S config.h defs.h$(CC) -E -traditional $< -o $@setup.s: setup.S config.h defs.h$(CC) -E -traditional $< -o $@memtest.bin: memtest_shared.bin bootsect.o setup.o memtest.bin.lds$(LD) -T memtest.bin.lds bootsect.o setup.o -b binary \memtest_shared.bin -o memtest.bin

这几个规则指明了源码目录中的obj文件如何链接成memtest86+.bin,而链接过程又由3个lds文件提供:

#memtest_shared.lds链接脚本:规则memtest_shared依赖的lds脚本
OUTPUT_FORMAT("elf32-i386");
OUTPUT_ARCH(i386);ENTRY(startup_32);
#指定$(OBJS)的入口点为head.S!startup_32
SECTIONS {. = 0;.text :
...
#重要的节,后面重定位时,会用got表中的信息对memtest_share进行重定向
.got : {*(.got.plt)*(.got)_edata = . ;}. = ALIGN(4);
..._end = .;}/DISCARD/ : { *(*) }
}

memtest_share规则生成memset_share文件,是由源码目录下所有.c文件和head.S链接后生成的,这个文件是标准的linux ELF文件

ubuntu:~/Desktop/memtest86+-4.20$ file memtest_shared
memtest_shared: ELF 32-bit LSB  shared object, Intel 80386, version 1 (SYSV), dynamically linked, not stripped
ubuntu:~/Desktop/memtest86+-4.20$ readelf -a memtest_shared|grep startup71: 00000000     0 NOTYPE  GLOBAL DEFAULT    1 startup_32 #这个符号在head.S中545: 00000000     0 NOTYPE  GLOBAL DEFAULT    1 startup_32

看下head.S的头几行

 .code32.globl startup_32
startup_32:cldcli

代码段开始就申明了标号startup_32,理论上memtest_share的入口点就是这个了

#memtest.lds链接脚本:规则memtest的依赖项
OUTPUT_FORMAT("elf32-i386");
OUTPUT_ARCH(i386);ENTRY(_start);
SECTIONS {. = 0x5000;_start = . ;.data : {*(.data)}
}

规则memtest仅仅把前一个规则生成的memtest_share作为输入链接到.data节

#memtest.bin链接脚本:规则memtest.bin的依赖项
OUTPUT_FORMAT("binary")
OUTPUT_ARCH("i386")ENTRY(_main);#指明入口点为_main
SECTIONS {. = 0;
#左边是输出:右边是输入
.bootsect : { *(.bootsect) }.setup : { *(.setup) }.memtest : { _start = . ;*(.data) _end = . ;}_syssize = (_end - _start + 15) >> 4;
}

这个链接脚本是整个memtest86+中最重要的一个脚本,makefile参考它生成了整个memtest86+二进制文件。同时还规定了源码目录下所有obj文件在bin文件中的布局:bootsect在最前端,紧接着是setup,最后是memtest_share。

既然知道了整个memtest86+文件分布,也知道了程序入口是bootsect.S!_main,那就可以分析整个程序的流程了,打开bootsect.S

#include "defs.h"
.code16 #告诉汇编器,这里要生成16bit代码
.section ".bootsect", "ax", @progbits
_boot:# ld86 requires an entry symbol. This may as well be the usual one.
.globl  _main
_main:movw  $BOOTSEG, %axmovw   %ax, %ds #ds=0x7c00movw    $INITSEG, %axmovw   %ax, %esmovw    $256, %cxsubw   %si, %sisubw    %di, %di

一堆汇编代码,还有一些奇奇怪怪的立即数,真像直接不干了!查找$BOOTSEG的定义,其值为0x07c0。汇编代码前2句是把0x7c0赋值给段寄存器ds

#define LOW_TEST_ADR 0x00002000      /* Final adrs for test code */#define BOOTSEG       0x07c0          /* Segment adrs for inital boot */
#define INITSEG     0x9000          /* Segment adrs for relocated boot */
#define SETUPSEG    (INITSEG+0x20)     /* Segment adrs for relocated setup */
#define TSTLOAD     0x1000          /* Segment adrs for load of test */#define KERNEL_CS    0x10            /* 32 bit segment adrs for code */
#define KERNEL_DS   0x18            /* 32 bit segment adrs for data */
#define REAL_CS     0x20            /* 16 bit segment adrs for code */
#define REAL_DS     0x28            /* 16 bit segment adrs for data */

如果你做过i386处理器,马上会想到bootsect.S和setup.S是一个启动cpu进入32位保护模式的bootloader,同时从磁盘上加载剩余的程序到内存设置程序运行环境!因为bootloader的功能大同小异,这里略过处理功能这些的代码。

setup.S最终会调用memtest.share!。前面说过memtest.share是linux elf文件格式,而memtest86+这个程序显然没有运行linux内核,因此为了运行elf文件,它需要自己实现loader的功能,把memtest.share加载到内存并读取got重定位表对其中的重定位信息进行重定位,如下:

#head.S
0:/* Load the GOT pointer */
#call-pop 获得程序运行时,当前指令在内存中的地址,以后ebx就作为重定位的参考地址
call    0f
0:  popl    %ebxaddl    $_GLOBAL_OFFSET_TABLE_+[.-0b], %ebx
...
leal    gdt@GOTOFF(%ebx), %eax
movl    %eax, 2 + gdt_descr@GOTOFF(%ebx)
lgdt    gdt_descr@GOTOFF(%ebx) #gdt_descr是一个需要重定位的变量
leal    flush@GOTOFF(%ebx), %eax

head.S就是通过这个办法对memtest.share中导出的重定位符号进行重定位。可以通过readelf -a查看重定位信息。

待到重定位结束,head.S通过call do_test进入main.c开始内存测试。

//下面是do_test的伪代码
void do_test(void)
{/*如果memtest是由grub启动的,grub会把grub.cfg中的启动参数存放到boot_param中,parse_command_line获得boot_param*/parse_command_line();switch(tseq[v->test].pat)
{
case N://具体的测试项
break;
}
window++; //前面setup.S通过e820获得的内存图,把内存分布存放到数组windows中,而windows是windows中的一项数组元素
//还有部分e820数组中的内存块没有在这一项test中测试过,通过run_at进行下一次测试
if (window != 0) {
run_at(LOW_TEST_ADR);
}
else
{
//e820数组中所有内存块都通过了这一项test测试,进入下一项测试
v->test++;
run_at(LOW_TEST_ADR);
}
}

上面的伪代码多次出现run_at,这是整个memtest86+中最奇葩的操作:

static void __run_at(unsigned long addr)
{/* Copy memtest86+ code */memmove((void *)addr, &_start, _end - _start);/* Jump to the start address */p = (ulong *)(addr + startup_32 - _start);goto *p;
}static unsigned long run_at_addr = 0xffffffff;
static void run_at(unsigned long addr)
{unsigned long start;unsigned long len;run_at_addr = addr;start = (unsigned long) &_start;len = _end - _start;if (   ((start < addr) && ((start + len) >= addr)) ||((addr < start) &&  ((addr + len) >= start))) {/* Handle overlap by doing an extra relocation */if (addr + len < high_test_adr) {__run_at(high_test_adr);}else if (start + len < addr) {__run_at(LOW_TEST_ADR);}}__run_at(run_at_addr);
}

获得_start标号的位置,然后重新跳到_start去运行。那么,标号_start定义在哪?反汇编看一下

objdump -d memtest_share
00000000 <_start>:0:  fc                      cld    1:   fa                      cli    2:   85 e4                   test   %esp,%esp4:  75 0c                   jne    12 <_start+0x12>6:    bc 5a c7 02 00          mov    $0x2c75a,%espb:  8d a4 24 20 20 00 00    lea    0x2020(%esp),%esp12: e8 00 00 00 00          call   17 <_start+0x17>17:   5b                      pop    %ebx18:  81 c3 49 a7 02 00       add    $0x2a749,%ebx1e: 8d a3 20 20 00 00       lea    0x2020(%ebx),%esp24: 8d 83 c8 5e fd ff       lea    -0x2a138(%ebx),%eax2a:   89 83 c2 5e fd ff       mov    %eax,-0x2a13e(%ebx)30:   0f 01 93 c0 5e fd ff    lgdtl  -0x2a140(%ebx)37:    8d 83 e1 58 fd ff       lea    -0x2a71f(%ebx),%eax3d:   6a 10                   push   $0x103f: 50                      push   %eax40:  cb                      lret   00000041 <flush>:41:   b8 18 00 00 00          mov    $0x18,%eax

_start定义在head.S中,并且就在head.S的开头。也就是,为了实现跳转,memtest_share跳到程序开头重新再跑一次!!!!尼玛,我都震惊了,好在windows数组和window是全局变量,要不然下次进到do_test都不知道上回测到哪了....

memtest86+4.20流程分析相关推荐

  1. 转:Android之 MTP框架和流程分析

    2019独角兽企业重金招聘Python工程师标准>>> 转载:http://www.cnblogs.com/skywang12345/p/3474206.html 概要 本文的目的是 ...

  2. Java版 QQ空间自动登录无需拷贝cookie一天抓取30WQQ说说数据流程分析【转】

    Java版 QQ空间自动登录无需拷贝cookie一天抓取30WQQ说说数据&流程分析 QQ空间说说抓取难度比较大,花了一个星期才研究清楚! 代码请移步到GitHub GitHub地址:http ...

  3. android6.0源码分析之Camera API2.0下的Preview(预览)流程分析

    1.Camera2 preview的应用层流程分析 preview流程都是从startPreview开始的,所以来看startPreview方法的代码: <code class="hl ...

  4. android6.0源码分析之Camera API2.0下的初始化流程分析

    1.Camera2初始化的应用层流程分析 Camera2的初始化流程与Camera1.0有所区别,本文将就Camera2的内置应用来分析Camera2.0的初始化过程.Camera2.0首先启动的是C ...

  5. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. mysql 8.0 一条insert语句的具体执行流程分析(三)

    代码版本:mysql 8.0.22 编程语言:c++ && c++11 && c++14 && c++17 上一篇文章:mysql 8.0 一条inse ...

  7. internetreadfile读取数据长度为0_Go发起HTTP2.0请求流程分析(后篇)——标头压缩

    阅读建议 这是HTTP2.0系列的最后一篇,笔者推荐阅读顺序如下: Go中的HTTP请求之--HTTP1.1请求流程分析 Go发起HTTP2.0请求流程分析(前篇) Go发起HTTP2.0请求流程分析 ...

  8. Android 8.0 学习(23)---recovery 流程分析

    Android 8.0 recovery 流程分析 这里主要分析non A/B模式下的recovery流程  A/B模式下的recovery在boot中  后续会不断补充,如果有疏漏或者错误的地方,请 ...

  9. Android 7.0系统启动流程分析

    随着Android版本的升级,aosp项目中的代码也有了些变化,本文基于Android 7.0分析Android系统启动流程.当我们按下电源键后,整个Android设备大体经过了一下过程:  今天我们 ...

最新文章

  1. combox qt 引起的删除失败_关于QT的QCombox的掉坑出坑
  2. 16.ajax_case08
  3. loadrunner中对https证书的配置
  4. CTS(1)---谷歌CTS测试简介
  5. windows sdk 学习笔记(8)
  6. Android 5.0 双卡信息管理分析
  7. mac php5.6 gd 扩展,mac 编译安装php5.6.40
  8. 中国酒器市场趋势报告、技术动态创新及市场预测
  9. java实现多线程抢单_来聊一聊3种实现JAVA多线程的方式吧
  10. 成都文理学院计算机一级还没考过,两次查成绩不一致,合格成不合格?成都文理学院官方回应...
  11. 奶块最新服务器叫什么,奶块5.4.0版本更新公告
  12. Tomcat安装及配置教程(超详细的图文教程)
  13. FreeIPA问题记录
  14. 计算机专硕都是数二英二吗,【专硕初试】大改革?英二、数二都不考了?
  15. VR虚拟现实培训解决方案
  16. 七牛云配置token-----CryptoJS.js
  17. [Android]我的第一个手机应用
  18. vi 经典配色 molokai.vim 配色安装
  19. super 关键字详解
  20. 95105105电话订票流程

热门文章

  1. 极客日报第6期:天猫京东双十一总交易额7697亿元,你贡献了多少?.NET 5.0 正式版发布!
  2. SM1,SM2,SM3,SM4 介绍
  3. [附源码]JAVA+ssm社区论坛(程序+Lw)
  4. 基于51系列单片机的(循迹、避障、蓝牙)智能小车(2)源代码
  5. 学术会议墙报_干货 | 如何打造一份完美的学术墙报?
  6. android 自定义组件圆形边框
  7. python读取文件夹下所有图像 预处理_在python中读取预处理的cr2原始图像数据
  8. Android3彩蛋,Android升级gradle5的坑+Androidstudio3.4小彩蛋
  9. linux 下db2数据库命令
  10. 从学渣到创办95亿独角兽企业,饿了么张旭豪是怎么做到的