add r0, pc, #OEMAddressTable-(.+8)指令引起的论坛讨论

来源:互联网 发布:mac电脑中病毒怎么杀毒 编辑:程序博客网 时间:2024/05/14 20:12

 作者:wogoyixikexie@gliet

    wangxin_801115在昨天发了个帖子,本来觉得很简单,没有想到引起了很大的关注。现在收集起来造福you and me。

———————————————————————————————————————————————————

add r0, pc, #OEMAddressTable-(.+8) ; (r0) = OEMAddressTable phys addr  *DO NOT DESTROY THIS VALUE
mov r11, r0 ; (r11) = &MemoryMap (save pointer for later)
请问这句话的详细意思是什么呢??
特别是-(.+8)
谢谢各位

————

这是ARM流水线。
如果add r0, pc, #OEMAddressTable
那么PC会得到OEMAddressTable+8的指令的地址。
为了符合实际,就要减回来
add r0, pc, #OEMAddressTable-(.+8)

——————————————————————

但是还有个问题:

1、OEMAddressTable是物理地址还是虚拟地址(内存地址)
2、OEMAddressTable是相对地址还是绝对地址
3、MMU现在是否还没被打开
4、如果OEMAddressTable是虚拟地址
  那么 PC+OEMAddressTable == 物理地址????
  (PC也是虚拟地址)
5、如果OEMAddressTable是物理地址
  那么是不是可以直接mov r0 #OEMAddressTable
——————————————————————

1、OEMAddressTable是虚拟地址还是物理地址取决与PC的地址
2、OEMAddressTable是相对地址还是绝对地址取决于他的使用方式
3、MMU没被打开
4、这样些是PIC规范,并不是PC的数值+ OEMAddressTable的绝对地址,而是PC加上到OEMAddressTable的偏移量,就是相当于取OEMAddressTable标号的地址
5、OEMAddressTable是一个标号,取标号的地址到寄存器有两种方法
LDR r0, [pc, #OEMAddressTable-(.+8)]
LDR r0, =OEMAddressTable

标号地址的问题我也迷糊,不知道我的理解对不对,

这个帖子和http://topic.csdn.net/u/20081023/12/32e215de-ddf3-4fbe-8992-a4384ce0c3ec.html 这个帖子一脉相承
从 StartUp 到 KernelStart

————————————

从OEMAddressTable出现地方,它是物理地址的。不知道是否因为在它编译的时候这个MMU都没有打开,这个东西的确有点奇怪的。
我发现是否是虚拟地址一般就会扯上MMU,只要一开MMU,立刻就是虚拟地址了。不知道这个微软的编译器是怎么做的?但是看了代码,事实就是这样——是否是虚拟地址和MMU是否开启有关,这个我有点纳闷。编译器真的可以这样弄的吗?
欢迎来讨论,不知道我的理解是否正确。
—————————————————

这是一条伪指令,作用是把OEMAddressTable的地址加载到r0。比较难理解的是(.+8)这个表达式。.其实代表当前指令的地址。比如说这条指令:
LABEL1
  LDR R0, .
作用是把LABEL1的地址加载到R0中。
这里还要知道一点ARM架构的特点,一条指令从加载到执行主要分为预取、译码、执行三个阶段。执行add r0, pc, #OEMAddressTable-(.+8)时,pc寄存器的值实际上指向下下条指令。另一方面,.是在MS的ARM编译器在预处理时决定的,它的值为当前指令的地址,因此有恒等式.+8=pc。
明白了这两点,这条指令就很容易理解了:
r0 = pc+OEMAddressTable-(.+8) = OEMAddressTable + (pc-(.+8)) = OEMAddressTable

搞得这么复杂,其实有一条更简单更好理解的指令和它等价:
adr r0, OEMAddressTable
微软有时候尽干傻事,简单的事情非要复杂化。

————————————————————

引用 11 楼 navi_dx 的回复:
ADR是一条伪指令,汇编器汇编时也会尝试用ADD或SUB指令来构造这个地址,如果构造地址失败就产生一个Error
MS这样写只是少转了一次弯



这个ADR伪指令作用范围有限制的。
超过一定距离、长度的标号他就有心无力了。
所以微软使用add r0, pc, #OEMAddressTable-(.+8)的办法有时候未必没有道理的。

———————————————————————————————————

引用 45 楼 Reallyu 的回复:
OEMAddressTable 是一个地址,不过是编译器产生的地址,可以理解为在flash中的地址
add r0, pc, #OEMAddressTable-(.+8) ;
这句执行的时候代码和数据刚从flash复制到ram中,OEMAddressTable也从flash复制到ram中了,这句代码是想从ram中取OEMAddressTable,所以要加上pc的偏移量(此时pc指向ram中的代码).



我觉得还是这样理解好了:OEMAddressTable是和目前(PC)代码的距离——总之不知道它是怎么实现这样的功能的,看上去OEMAddressTable像是动态的,使用“OEMAddressTable是和目前(PC)代码的距离”解释较好。

——————————————————————————————————————————

——最后收尾
由于这个OEMAddressTable是MMU开启之前编译了,故add r0, pc, #OEMAddressTable-(.+8)整个表达式使
r0得到的是物理地址——因为在kernelStart里面的确是要把r0转换成虚拟地址的(根据OEMAddressTable这个表来转换的)。

——下面是楼主引用的某位大侠的文章,不错,一切就这么明了了(以前从不知道R9的作用)

 

ARM的位置无关程序设计在Bootloader中的应用(引)


ARM处理器支持位置无关的程序设计,这种程序加载到存储器的任意地址空间都可以正常运行,其设计方法在嵌入式应用系统开发中具有重要的作用。本文首先介绍位置无关代码的基本概念和实现原理,然后阐述基于ARM汇编位置无关的程序设计方法和实现过程,最后以嵌入式Bootloader程序设计为例,介绍位置无关程序设计在Bootloader程序设计中的作用。

引言

  基于位置无关代码PIC(PositionIndependent Code)的程序设计在嵌入式应用系统开发中具有重要的作用,尤其在裸机状态下开发Bootloader程序及进行内核初始化设计;利用位置无关的程序设计方法还可以在具体应用中用于构建高效率动态链接库,因而深入理解和熟练掌握位置无关的程序设计方法,有助于开发人员设计出结构简单、清晰的应用程序。本文首先介绍位置无关代码的基本概念和实现原理,然后阐述基于ARM汇编位置无关的程序设计方法和实现过程,最后以Bootloader程序设计为例,介绍了位置无关程序设计在Bootloader程序设计中的作用。

1 位置无关代码及程序设计方法

1.1 基本概念与实现原理

  应用程序必须经过编译、汇编和链接后才变成可执行文件,在链接时,要对所有目标文件进行重定位(relocation),建立符号引用规则,同时为变量、函数等分配运行地址。当程序执行时,系统必须把代码加载到链接时所指定的地址空间,以保证程序在执行过程中对变量、函数等符号的正确引用,使程序正常运行。在具有操作系统的系统中,重定位过程由操作系统自动完成。

  在设计Bootloader程序时,必须在裸机环境中进行,这时Bootloader映像文件的运行地址必须由程序员设定。通常情况下,将 Bootloader程序下载到ROM的0x0地址进行启动,而在大多数应用系统中,为了快速启动,首先将Bootloader程序拷贝到SDRAM中再运行。一般情况下,这两者的地址并不相同,程序在SDRAM中的地址重定位过程必须由程序员完成。实际上,由于Bootloader是系统上电后要执行的第一段程序,Bootloader程序的拷贝和在这之前的所有工作都必须由其自身来完成,而这些指令都是在ROM中执行的。也就是说,这些代码即使不在链接时所指定的运行时地址空间,也可以正确执行。这就是位置无关代码,它是一段加载到任意地址空间都能正常执行的特殊代码。

  位置无关代码常用于以下场合:

  ◆ 程序在运行期间动态加载到内存;
  ◆ 程序在不同场合与不同程序组合后加载到内存(如共享的动态链接库);
  ◆ 在运行期间不同地址相互之间的映射(如Bootloader程序)。

  虽然在用GCC编译时,使用-fPIC选项可为C语言产生位置无关代码,但这并不能修正程序设计中固有的位置相关性缺陷。特别是汇编语言代码,必须由程序员遵循一定的程序设计准则,才能保证程序的位置无关性。

  1.2 ARM处理器的位置无关程序设计要点

  ARM程序的位置无关可执行文件PIE(PositionIndependent Executable)包括位置无关代码PIC和位置无关数据PID(PositionIndependent Data)两部分。

  PID主要针对可读写数据段(.data段),其中保存已赋初值的全局变量。为实现其位置无关性,通常使用寄存器R9作为静态基址寄存器,使其指向该可读写段的首地址,并使用相对于基址寄存器的偏移量来对该段的变量进行寻址。这种方法常用于为可重入程序的多个实例产生多个独立的数据段。在程序设计中,一般不必考虑可读写段的位置无关性,这主要是因为可读写数据主要分配在SDRAM中。

  PIC包括程序中的代码和只读数据(.text段),为保证程序能在ROM和SDRAM空间都能正确运行(如裸机状态下的Bootloader程序),必须采用位置无关代码程序设计。下面重点介绍PIC的程序设计要点。

  PIC遵循只读段位置无关ROPI(ReadOnly Position Independence)的ATPCS(ARMThumb Procedure Call Standard)的程序设计规范:

  (1) 程序设计规范

  引用同一ROPI段或相对位置固定的另一ROPI段中的符号时,必须是基于PC的符号引用,即使用相对于当前PC的偏移量来实现跳转或进行常量访问。

  ① 位置无关的程序跳转。在ARM汇编程序中,使用相对跳转指令B/BL实现程序跳转。指令中所跳转的目标地址用基于当前PC的偏移量来表示,与链接时分配给地址标号的绝对地址值无关,因而代码可以在任何位置进行跳转,实现位置无关性。

  另外,还可使用ADR或ADRL伪指令将地址标号值读取到PC中实现程序跳转。这是因为ADR或ADRL等伪指令会被编译器替换为对基于PC的地址值进行操作,但这种方式所能读取的地址范围较小,并且会因地址值是否为字对齐而异。但在ARM程序中,使用LDR等指令直接将地址标号值读取到 PC中实现程序跳转不是位置无关的。例如:

  LDRPC, =main

  上面的LDR汇编伪指令编译后的结果为:

  LDRPC, [PC, OFFSET_TO_LPOOL]
  LPOOLDCD main

  可见,虽然LDR是把基于PC的一个存储单元LPOOL的内容加载到PC中,但该存储单元中保存的却是链接时所决定的main函数入口的绝对地址,所以main函数实际所在的段不是位置无关。

  ② 位置无关的常量访问。在应用程序中,经常要读写相关寄存器以完成必要的硬件初始化。为增强程序的可读性,利用EQU伪指令对一些常量进行赋值,但在访问过程中,必须实现位置无关性。下面以PXA270的GPIO初始化介绍位置无关的常量访问方法。

  GPIO_BASEEQU0x40e00000;
  GPIO基址寄存器地址GPDR0EQU0x00c;相对于GPIO基址寄存器的偏移量
  init_GPDR0EQU0xfffbfe00;寄存器GPDR0初值
  LDRR1, =GPIO_BASE
  LDRR0, =init_GPDR0
  STRR0, [R1, #GPDR0]

  上述汇编代码段经编译后的结果为:

  LDRR1, [PC, OFFSET_TO_GPIO_BASE]
  LDRR0, [PC, OFFSET_TO_init_GPDR0]
  STRR0, [R1, #0xc]
  GPIO_BASEDCD0x40e00000
  GPDR0DCD0x00c
  init_GPDR0DCD0xfffbfe00

  可见,LDR伪指令实际上使用基于PC的偏移量来对符号常量GPIO_BASE和init_GPDR0进行引用,因而是位置无关的。由此可以得出如下结论:使用LDR伪指令将一个常量读取到非PC的其他通用寄存器中可实现位置无关的常量访问;但将一个地址值读取到PC中进行程序跳转时,跳转目标则是位置相关的。

  (2) 程序设计规范2

  其他被ROPI段中的代码引用的必须是绝对地址,或者是基于可读写位置无关(RWPI)段的静态基址寄存器的可写数据。

  使用绝对地址只能引用被重定位到特定位置的代码段中的符号,通过在位置无关代码中引入绝对地址,可以让程序跳转到指定位置。例如,假设 Bootloader的阶段1将其自身代码拷贝到链接时所指定的SDRAM地址空间后,当要跳转到阶段2的C程序入口时,可以使用指令“LDRPC, =main”跳转到程序在SDRAM中的main函数入口地址开始执行。这是因为程序在编译链接时给main函数分派绝对地址,系统通过将main函数的绝对地址直接赋给PC实现程序跳转。如果使用相对跳转指令“Bmain”,那么只会跳转到启动ROM内部的main函数入口

  2 位置无关代码在Bootloader设计中的应用

  在使用GNU工具开发Bootloader时,程序在链接时会通过一个链接脚本(linker script)来设定映像文件的内存映射。一个简单的链接脚本结构如下:

  OUTPUT_ARCH(arm)
  ENTRY(_start)
  SECTIONS {
    . = BOOTADDR;/*Bootloader的起始地址*/
    __boot_start = .;
    .textALIGN(4): {/*代码段.text*/
      *(.text)
    }
    .dataALIGN(4): {/*数据段.data*/
      *(.data)
    }
    .gotALIGN(4): {/*全局偏移量表.got段*/
      *(.got)
    }
    __boot_end = .;/*Bootloader映像文件的结束地址*/
    .bssALIGN(16): {/*堆栈段.bss*/
      __bss_start = .;
      *(.bss)
    __bss_end =.;
    }
  }

  这里不再介绍链接脚本的语法。需要指出的是,链接脚本中所描述的输出段地址为虚拟地址VMA(Virtual Memory Address)。这里的“虚拟地址”仅指映像文件执行时,各输出段所重定位到相应的存储地址空间,与内存管理无关。因此,上面的链接脚本实际上指定了 Bootloader映像在执行时,将被重定位到BOOTADDR开始的存储地址空间,以保证在相关位置对符号进行正确引用,使程序正常运行。

  ARM处理器复位后总是从0x0地址取第1条指令,因此只需把BOOTADDR设置为0,再把编译后生成的可执行二进制文件下载到ROM的 0x0地址开始的存储空间,程序便可正常引导;但是,一旦在链接时指定映像文件从0x0地址开始,那么Bootloader就只能在0x0地址开始的 ROM空间内运行,而无法拷贝到SDRAM空间运行实现快速引导。当然,对PXA270等具有MMU功能的微处理器来说,虽然可以先将 Bootloader映像整个拷贝到SDRAM中,再使用MMU功能将SDRAM空间映射到0x0地址,进而继续在SDRAM中运行;但这样一方面会使得 Bootloader的设计与实现复杂化,另一方面在一些必须屏蔽MMU功能的应用中(例如引导armlinux系统),无法使用MMU进行地址重映射。

  利用ARM的基于位置无关的程序设计可以解决上述问题。只需在程序链接时,将BOOTADDR设置为SDRAM空间的地址(一般情况下利用 SDRAM中最高的1 MB存储空间作为起始地址),这样ARM处理器上电复位后Bootloader仍然可以从地址0开始执行,并将自身拷贝到指定的__boot_start 起始的SDRAM中运行。实现上述功能的链接脚本所对应的启动代码架构如下:

  .section .text
  .globl _start
  _start:
  Breset/*复位异常*/
  /*其他异常处理代码*/
  reset:
  /*复位处理程序*/
  copy_boot:/*拷贝Bootloader到SDRAM*/
  LDRR0, =0x0LDRR1, =__boot_start
  LDRR2, =__boot_end
  1:LDRMIA R0!, { R3-R10 }
  STRMIA R1!, { R3-R10 }
  CMPR1, R2
  BLT1b
  clear_bss:
  /*清零.bss段*/
  BL
  init_Stack/*初始化堆栈*/
  LDRPC, = main/*跳转到阶段2的C程序入口*/
  .end

  程序入口为_start,即复位异常,所有其他异常向量都使用相对跳转指令B来实现,以保证位置无关特性。在完成基本的硬件初始化后,利用链接脚本传递过来__boot_start和__boot_end的参数,将Bootloader映像整个拷贝到指定的SDRAM空间,并清零.bss段,初始化堆栈后,程序将main函数入口的绝对地址赋给PC,进而跳转到SDRAM中继续运行。程序在跳转到main函数之前,所有的代码都在ROM中运行,因而必须要保证代码的位置无关性,所以在调用初始化GPIO、存储系统和堆栈等子程序时,都使用相对跳转指令来完成。

  使用位置无关设计Bootloader程序有如下优点:

  ① 简化设计,方便实现系统的快速引导。位置无关代码可以避免在引导时进行地址映射,并方便地跳转到SDRAM中实现快速引导。
  

  ② 实现复位处理智能化。由于位置无关代码可以被加载到任意地址空间运行,因此其运行时的当前地址与链接时所指派的地址并不一定相同。利用这一特性,可以在复位处理程序中使处理器进入SVC模式并关闭中断后加入如下代码,便可根据当前运行时的地址进行不同的复位处理:

  ADRR0, _start/*读取当前PC附近的_start标号所在指令地址*/
  LDRR1,=__boot_start/*读取Bootloader在SDRAM的起始地址*/
  CMPR0,R1
  BEQclear_bss

  上述代码中的ADR指令读取的_start标号地址由指令的执行地址决定。若是从SDRAM中的Bootloader启动,则上述比较结果相等,程序直接跳转到clear_bss标号地址处执行,这样可以避免存储系统的重新初始化和Bootloader的拷贝过程;若是上电或硬件复位,程序从 ROM启动,则上述比较结果不等,程序便进行包括系统初始化和Bootloader拷贝等过程的全面复位处理操作。

  ③ 便于调试。Bootloader的调试通常也是一个繁琐的过程,使用位置无关代码,则可以将映像文件加载到SDRAM中进行调试,这既能真实地反映程序从ROM中进行系统引导的情况,又可以避免频繁烧写程序存储器。