首页 > 其他 > 详细

or1200开发环境的使用

时间:2014-03-21 15:13:59      阅读:545      评论:0      收藏:0      [点我收藏+]

以下内容摘自《步步惊芯——软核处理器内部设计分析》一书


1、一个简单的汇编程序

      上一节安装好了GNU编译工具,还设置了虚拟机与宿主机的共享文件夹,下面我们就小试牛刀,写一个简单的程序,需要读者朋友们了解的是本书所有的测试代码都是很简单的汇编程序,因为即使最简单C语言代码(如:HelloWorld)经过编译后也会得到大量目标代码,而我们的目的是了解处理器内部的工作过程,所以大量的目标代码容易分散我们的精力,为此示例代码一律采用汇编,有的甚至只有几条指令,但也能说明问题。在Ubuntu中新建一个Document,文件名可以为Example.S,输入下面的代码,我们的第一个汇编程序。



.section .text,"ax"    #定义了一个Section,Section名为.text,并且是可执行Section
	.org 0x100             #OR1200处理器默认从地址0x100处开始执行,所以我们的代码从0x100开始
.global _start
_start:               
      l.andi r0,r0,0      #OR1200的r0寄存器应该始终为0,所以这里将其设置为0
      l.extwz r1,r0       #下面两条指令是初始化寄存器r1、r2,使其都为0
      l.extwz r2,r0
      l.addi r1,r1,0x0A   #r1的值加上0x0A存储到r1中,使得r1为0x0A
      l.add r2,r2,r1      #r1的值加上r2的值存储到r2中,使得r2为0x0A
      l.nop 0x0001        #空指令,但对模拟器有特殊含义

      通过注释可知上述代码非常简单,只是几个简单的寄存器操作指令,现在不懂没有关系,只需要知道程序执行最后使得寄存器r1、r2都为0x0A,借助这么一个简单的程序,足够我们了解编译、链接、模拟器执行、仿真的全部步骤了。


2、编译及ELF文件介绍

      在终端中首先使用cd命令将路径调整到上述Example.S所在目录,然后使用如下指令编译代码:


or32-elf-as Example.S –o Example.o


      上述指令得到目标代码Example.o。使用UE或者记事本打开Example.o文件,可以发现其最初的四个字节是:0x7F、0x45、0x4C、0x46,这说明Example.o是一个ELF文件。


bubuko.com,布布扣

图2.11 Example.o的开始部分

 

      遇到什么学什么,这是笔者的一贯宗旨,下面就简单介绍一下ELF文件,读者朋友中如果谁对这不感兴趣或者希望尽快了解编译链接过程的可以跳过下面的分析,直接阅读2.2.3节。

      ELF(Executable and Linkable Format)可执行链接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(ABI:Application Binary Interface)而开发和发布的。ELF目标文件有三种类型:

      (1)可重定位(Relocatable)文件:保存着代码和适当的数据,用来和其他Object文件一起创建一个可执行文件或共享文件。

      (2)可执行(Executable)文件:保存着一个用来执行的程序,该文件指出了如何来创建程序进程映象。

      (3)共享目标文件:包含了在两种使用环境中链接的代码和数据。首先链接器(ld)可以将它和其余可重定位文件和共享目标文件一起处理,生成另外一个目标文件(比如:编译器和链接器把*.o和*.so一起装配成一个*.exe文件)。其次,动态链接器(Dynamic Linker)可将它与某个可执行文件以及其它共享目标文件组合在一起创建进程映像(比如:动态加载器把.exe程序和*.so加载进内存执行)。

      无论何种类型的ELF文件,其结构都是相同的。ELF文件由四部分组成:ELF header、Program header table、Sections、Section header table。其最开始的部分就是ELF header,定义如下:

#define EI_NIDENT 16
typedef struct{
       unsigned char     e_ident[EI_NIDENT];   
       Elf32_Half        e_type;                      //Elf32_Half表示是2个字节大小
       Elf32_Half        e_machine;
       Elf32_word        e_version;                   //Elf32_Word表示是4个字节大小
       Elf32_Addr        e_entry;                     //Elf32_addr也表示4个字节大小
       Elf32_Off         e_phoff;                     //Elf32_Off也表示4个字节大小
       Elf32_Off         e_shoff;
       Elf32_Word        e_flags;
       Elf32_Half        e_ehsize;
       Elf32_Half        e_phentsize;
       Elf32_Half        e_phnum;
       Elf32_Half        e_shentsize;
       Elf32_Half        e_shnum;
       Elf32_Half        e_shstrndx;
}Elf32_Ehdr;


      开始四个字节是固定不变的:0x7F,紧接着是ELF三个字符的ASCII码,这四个字节表明这个文件是一个ELF文件。此处以Example.o为例,介绍Elf header后面的字节含义,参考图2.11:

  •  e_type是01,表示是可重定位文件
  •  e_machine表示运行该程序需要的体系结构,此处为0x5C,就是OpenRISC
  •  e_version表示文件版本,此处是1
  •  e_entry表示程序的入口地址,此处是0x0
  •  e_phoff是Program header table在文件中的偏移量(以字节计数),此处是0x0
  •  e_shoff是Section header table在文件中的偏移量(以字节计数),此处为0x0178
  •  e_flags为0
  •  e_ehsize表示ELF header的大小,此处为0x34
  •  e_phentsize表示Program header table中每一个条目(一个Program header)的大小,此处为0x0
  •  e_phnum表示Program header table中有多少个条目,此处为0
  •  e_shensize表示Section header table中每一个条目(一个Section header)的大小,此处为0x28
  •  e_shnum表示Section header table中有多少个条目,此处为0x07
  •  e_shstrndx保存着字符表相关入口的节区头部表索引,此处为0x04

      通过上述解释可以了解到这个文件是一个可重定向文件(Relocatable)文件,不是可执行文件,同时了解该文件包含的Program header table、Section header table信息。这里没有Program header table,按照给出的偏移信息,我们可以得到Section header table表的位置,通过Section header table得到每个Section的位置。

      当然按照ELF header的内容以及Section header table,我们可以按图索骥地分析所有Section,但是这样效率太慢,借助于GNU工具链中的or32-elf-readelf,我们可以直接得到Section信息,如图2.12所示。


bubuko.com,布布扣

图2.12 利用程序or32-elf-readelf可以得到所有的Section信息


      注意添加“-S”参数。这里列出了7个Section的信息,注意其中的“.text”这个Section,它的起始地址是0x34,长度是0x118,我们列出这个Section的内容如图2.13所示。


bubuko.com,布布扣

图2.13 Section .text的内容


      在这0x118个字节中,前0x100个字节都是0x00,接下来的24个字节是什么?我们利用工具or32-elf-objdump对目标代码进行反汇编,得到指令与二进制代码的对应关系,如图2.14所示。


bubuko.com,布布扣

图2.14 使用Objdump查看反汇编结果


      这里注意加上参数“-d”表示显示可执行Section的反汇编结果。显示出来的结果分为三栏,左边是指令执行时的地址,在程序中我们的第一条指令是从0x100开始的,中间一栏是对应的二进制代码,右边一栏是对应的汇编指令,对比一下图2.13与图2.14,可以发现Section .text的最后24个字节正是这6条汇编指令。


3、链接

      通过编译我们得到了一个可重定位的ELF文件,但这个文件还不能执行,需要通过链接转化为可执行文件,然后才能执行。使用or32-elf-ld完成这项工作,在or32-elf-ld的参数中需要声明一个链接描述脚本,链接描述脚本描述了输入文件的各个Section如何映射到输出文件的各Section中,并控制输出文件中Section和符号的内存布局。可以通过新建一个Document作为链接描述脚本,文件名为ram.ld,内容如下:


MEMORY
        {       
        ram    : ORIGIN = 0x00000000, LENGTH = 0x00005000
        }

SECTIONS
{
	.text :
        {
        *(.text)
        } > ram

        .data :
        {
        *(.data)
        } > ram

        .bss :
        {
        *(.bss)
        } > ram
}

Entry(_start)

      这里定义了一个存储块——ram,其起始地址是0x0,长度是0x5000,然后指示链接器输出文件包含三个Section,分别是.text、.data、.bss,其中.text从ram的起始地址开始存放,后面跟着.data、.bss,并且输入文件的Section .text存放在输出文件的.text中,输入文件的Section .data存放在输出文件的.data中,输入文件的Section .bss存放在输出文件的.bss中。最后的Entry指定程序的入口地址,也就是第一条执行指令的地址是_start符号的值,从汇编代码中可知_start符号就是0x100。现在就可以使用链接器了,在终端中输入如下命令:


or32-elf-ld –T ram.ld Example.o –o Example.or32


      得到链接后的文件Example.or32,这也是一个ELF格式的文件,其Elf header,如图2.15所示。


bubuko.com,布布扣

图2.15 Example.or32的Elf header


      在分析Example.o的Elf header时我们是手工分析的,主要是为了大家的理解,现在可以直接使用工具分析Elf header,在终端中输入如下命令:


or32-elf-readelf –h Example.or32

      这里加上参数“-h”表示只读取Elf header,得到结果如图2.16所示。


bubuko.com,布布扣

图2.16 Example.or32的Elf header信息


      其中显示是一个可执行文件。读者朋友们可能注意到了,Example.or32比Example.o多了Program header,而这在Example.o里面是没有的,与Section header一样,Program header也可以使用一个结构体描述,如下:

typedef struct{
       Elf32_Word          p_type;
       Elf32_Off           p_offset;
       Elf32_Addr          p_vaddr;
       Elf32_Addr          p_paddr;
       Elf32_Word          p_filez;
       Elf32_Word          p_memsz;
       Elf32_Word          p_flags;
       Elf32_Word          p_align;
}Elf32_Phdr;


      我们还是使用or32-elf-readelf从Example.or32中分析出一个Program header,然后结合这个Program header解释上面各个各项的含义。使用如下命令得到Program header的信息:


or32-elf-readelf –l Example.or32

      这里注意加上“-l”参数,表示列出Program header的信息,显示如图2.17所示。

bubuko.com,布布扣

图2.17 Program header信息


      借助上图介绍Program header各个字段的含义:

  •  p_type为LOAD,表示可加载
  •  p_offset表示段的第一个字节在文件Example.or32中的偏移,此处为0x2000
  •  p_vaddr表示段的第一个字节在内存中地址,此处为0
  •  p_paddr为0,在物理地址定位有关联的系统中,该成员是为该段的物理地址而保留的
  •  p_filez表示段在文件中的长度,此处为0x118
  •  p_memsz表示段在内存中的长度,此处为0x118
  •  p_flags为RE,表示可读、可执行
  •  p_align为0x2000,根据此项确定段在文件以及内存中如何对齐

      该Program header表示将Example.or32从地址0x2000开始的0x118个字节放置在内存的0x0处,打开Example.or32可以发现从0x2000开始的0x118个字节的内容与Example.o中Section .text的内容一样,所以当这个Program Section加载入内存后,会使得内存的0x100处存放的就是我们的第一条指令,而Example.or32的入口地址正是0x100。

      分析到这里,大家是不是对我们的编译、链接过程有了比之前更深的了解?其实这些与我们分析OR1200关系不大,但是有时候剖析也会上瘾,拿到什么都想拆开看看里面是什么,所以笔者就情不自禁的讲了这么大一段,读者朋友如果没有这样的兴趣,那么只需要知道编译和链接的命令就可以了,很简单,重复如下:

编译:or32-elf-as Example.S –o Example.o
链接:or32-elf-ld –T ram.ld Example.o –o Example.or32

4、Makefile文件

      为了得到可执行代码,我们需要输入两个指令:编译指令as、链接指令ld,还是有点麻烦,最好只输入一条指令就可以了,这需要使用到Makefile文件。还是先给出Makefile文件,然后作出解释。在Example.S所在目录下新建一个Document,文件名为Makefile,内容如下:

ifndef CROSS_COMPILE
CROSS_COMPILE = or32-elf-
endif
CC = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld

OBJECTS = Example.o

export	CROSS_COMPILE
# ********************
# Rules of Compilation
# ********************
all: Example.or32

%.o: %.S
	$(CC) $< -o $@

Example.or32: ram.ld $(OBJECTS)
	$(LD) -T ram.ld $(OBJECTS) -o $@

clean:
	rm -f *.o *.or32

      这是一个很简单的Makefile,借助于它介绍Makefile的组成。Makefile的前半部分是一些变量的定义,比如:定义CC为or32-elf-as,定义LD为or32-elf-ld,引用一个预定义的变量需要使用符号$。在文件的后半部分定义了多个目标,有all、clean等,采用的语法如下:


目标:依赖文件
      命令

      上述形式表示的意思是:(1)要想得到“目标”,那么需要执行“命令”;(2)“目标”依赖于“依赖文件”,当“依赖文件”中至少一个文件比“目标”文件新时,“命令”才被执行。在上面Makefile的“命令”中使用了Makefile一些预定义的变量,含义如下:


$<     表示第一个依赖文件的名称
$@     表示目标的完整名称

      所以上述Makefile可以解读如下:

       (1)用户输入make all,要求得到目标all,目标all的依赖文件是Example.or32,要先得到Example.or32

      (2)要得到Example.or32,依赖文件$(OBJECTS),也就是Example.o

      (3)要得到Example.o依赖于文件Example.S,这里已经提供了Example.S,满足依赖条件,然后通过执行命令$(CC) $< -o $@,实际就是or32-elf-as Example.S –oExample.o得到Example.o

      (4)得到Example.o后就可以进一步得到Example.or32,通过执行命令$(LD) -T ram.ld $(OBJECTS) -o$@,实际就是or32-elf-ld-T ram.ld Example.o –o Example.or32得到Example.or32

      (5)得到Example.or32,满足了目标all的依赖条件,从而实现目标all。

      有了Makefile文件,我们在终端中输入“make all”就可以完成编译、链接的过程了。

5、使用OR1KSim模拟器运行程序

      急性子的朋友一定早等的不耐烦了,别着急,现在就可以运行我们的第一个程序了,使用OR1KSim,这是一个OpenRISC架构的模拟器,已经在Ubuntu虚拟机中安装好了,在终端中输入如下命令:


sim –t Example.or32 –m1M > Example.trace

      参数“-t”表示跟踪每一条指令的执行,“Example.or32”表示要执行的文件,“-m1M”表示增加1M内存,“>Example.trace”表示跟踪信息输出到文件Example.trace文件中。上述命令执行完后会自动结束,然后可以打开文件Example.trace,显示如下内容:

Seeding random generator with value 0x07204ad8
Or1ksim 2011-08-15
Building automata... done, num uncovered: 0/213.
Parsing operands data... done.
Resetting PIC.
loadcode: filename Example.or32  startaddr=00000000  virtphy_transl=00000000
Not COFF file format
ELF type: 0x0002
ELF machine: 0x005c
ELF version: 0x00000001
ELF sec = 5
Section: .text, vaddr: 0x00000000, paddr: 0x0 offset: 0x00002000, size: 0x00000118
S 00000100: a4000000   l.andi  r0,r0,0         r0          = 00000000  flag: 0
S 00000104: e020004d   l.extwz r1,r0          r1          = 00000000  flag: 0
S 00000108: e040004d   l.extwz r2,r0          r2          = 00000000  flag: 0
S 0000010c: 9c21000a   l.addi  r1,r1,0xa     r1         = 0000000a  flag: 0
S 00000110: e0420800   l.add   r2,r2,r1       r2          = 0000000a  flag: 0

exit(0)
@reset : cycles 0, insn #0
@exit  : cycles 5, insn #6
 diff  : cycles 5, insn #6


      注意上面加粗显示的部分,一共有5行,每一行的内容依次是:当前处于特权模式还是用户模式、处理器要取的指令地址(即PC值)、指令的二进制编码、指令的汇编代码、改变的寄存器、改变的寄存器的新值、flag标志位的值。我们以第1行为例,如下:


S:              表示出于特权模式               
PC值:           0x00000100
指令的二进制编码:0xa4000000
指令的汇编代码:  l.addi r0,r0,0
目的地址:        r0
目的地址的新值:  0x00000000
flag的值:       0x0

      每执行一条指令PC加4,表示取下一条指令,程序执行最后r1为0xa,r2为0xa,满足预期。在源代码中还有一条空指令“l.nop 0x0001”,该指令只是告诉模拟器退出执行。这样通过OR1KSim模拟器就可以验证程序能不能得到预期结果。

or1200开发环境的使用,布布扣,bubuko.com

or1200开发环境的使用

原文:http://blog.csdn.net/leishangwen/article/details/21713357

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!