首页 > 其他分享 >memtest86+4.20流程分析

memtest86+4.20流程分析

时间:2022-11-07 20:38:28浏览次数:57  
标签:memtest addr 流程 memtest86 start test 4.20 shared ebx


    公版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.o

all: 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_shared
objcopy -O binary $< memtest_shared.bin

memtest: 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 startup
71: 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:
cld
cli

代码段开始就申明了标号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, %ax
movw %ax, %ds #ds=0x7c00
movw $INITSEG, %ax
movw %ax, %es
movw $256, %cx
subw %si, %si
subw %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 %ebx
addl $_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,%esp
4: 75 0c jne 12 <_start+0x12>
6: bc 5a c7 02 00 mov $0x2c75a,%esp
b: 8d a4 24 20 20 00 00 lea 0x2020(%esp),%esp
12: e8 00 00 00 00 call 17 <_start+0x17>
17: 5b pop %ebx
18: 81 c3 49 a7 02 00 add $0x2a749,%ebx
1e: 8d a3 20 20 00 00 lea 0x2020(%ebx),%esp
24: 8d 83 c8 5e fd ff lea -0x2a138(%ebx),%eax
2a: 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),%eax
3d: 6a 10 push $0x10
3f: 50 push %eax
40: cb lret

00000041 <flush>:
41: b8 18 00 00 00 mov $0x18,%eax

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



标签:memtest,addr,流程,memtest86,start,test,4.20,shared,ebx
From: https://blog.51cto.com/u_13927568/5831305

相关文章

  • SetupDiCallClassInstaller处理流程
      MS将于今年10月推出win10RS3RTM版,并要求OEM厂商的驱动程序必须支持新的D/C/H/U驱动框架(微软爸爸一声令下,苦了我们)。其中的"C"项要求过滤驱动的inf文件必须以扩展I......
  • MySQL_流程控制_分支结构
    1If结构功能:实现简单的双分支语法:IF(表达式1,表达式2,表达式3)执行顺序:如果表达式1成立,则IF函数返回表达式2的值,否则返回表达式3的值应用:任何地方 2case结构情况1......
  • MySQL_流程控制_循环结构
    分类WHILELOOPREPEAT 循环控制:Iterate类似于continue,继续,结束本次循环,继续下一次Leave类似于break,跳出,结束当前所在的循环 1WHILE语法【标签:】WHILE循环条......
  • 基于案例分析 MySQL Group Replication 的故障检测流程
    故障检测(FailureDetection)是GroupReplication的一个核心功能模块,通过它可以及时识别集群中的故障节点,并将故障节点从集群中剔除掉。如果不将故障节点及时剔除的话,一方面......
  • 3d仿真技术在生产工艺流程中的优势
    随着计算机仿真技术、计算机图形学和传感技术的不断发展,虚拟仿真技术越来越多地被应用到各个领域。虚拟仿真是由计算机来模拟现实世界,用户可通过各种传感设备和虚拟世界进......
  • 流程图bpmn
    bpmn流程图官网//引入流程图及汉化方案importModelerfrom'bpmn-js/lib/Modeler'//引入节点属性面板importpropertiesPanelModulefrom'bpmn-js-properties-pane......
  • 阿里服务器安装宝塔面板流程
    购买完阿里服务器后,通过宝塔面板可以很轻松操作服务器。将安装流程总结下。阿里云服务器centos(liunx)系统安装宝塔安装宝塔面板需要重置系统,首先应该备份数据。一.到阿里......
  • 项目流程管理工具:OmniPlan Pro 4 for Mac 中文
    OmniPlanPro4forMac是应用在Mac上的最好用的项目流程管理工具,以拖放支持为特点,对您日常工作想法做记录,帮助我们创建合乎逻辑的项目计划管理,OmniPlan4Mac版全新设计,拖......
  • Idea 打War包流程
    使用java为小程序提供接口,完成本地开发后,需要打war包上传到服务器。将流程记录下。1.Idea中选择ProjectStructure   2.选择Artifaces -> + ->WebApplic......
  • 研发流程
    研发流程   从一个几人开发的小公司到几百人规模中几十人研发的团队,感受到单打独斗和团队开发有明显的区别   团队开发有一套系统规范的流程:  1)运营通过......