什么是可重定位和绝对机器代码?

Sha*_*med 12 assembly relocation

在学习汇编程序时,我遇到了这些术语.我得到的想法是这样的,在可重定位机器代码中,代码不依赖于静态RAM位置.汇编程序指定我的程序的RAM需求.可以将内存放置在链接器为它们找到空间的任何位置.

这个想法是否正确?如果是这样,汇编程序如何完成?

而且,Absolute Machine Code的一些例子是什么?

old*_*mer 25

许多/大多数指令集具有pc相对寻址,意味着获取程序计数器的地址,该地址与您正在执行的指令的地址相关,然后为其添加偏移并使用它来访问内存或分支或类似那.这就是你所谓的可重定位.因为无论地址空间中的指令在哪里,你想要跳转的东西都是相对的.将整个代码块和数据块移动到其他地址,它们仍然相对相同的距离,因此相对寻址仍然有效.如果相等跳过,则下一条指令适用于这三条指令的任何位置(如果跳过,则跳过一条,跳过后一条).

绝对使用绝对地址,跳转到这个确切的地址,从这个确切的地址读取.如果相等则转为0x1000.

汇编程序并不是编译器和程序员所做的.通常,最终编译的代码最终将具有绝对寻址,特别是如果您的代码由链接在一起的单独对象组成.在编译时,编译器无法知道对象将在何处结束,也无法知道外部引用的位置或距离有多远,因此通常假设它们足够接近pc相对寻址(通常具有范围限制) .因此编译器通常会为链接器生成占位符以填充绝对地址.它取决于操作和指令集以及如何解决此外部地址问题的一些其他因素.最终,虽然基于项目大小,链接器最终会得到一些绝对寻址.因此,非默认值通常是一个命令行选项,用于生成与位置无关的代码-PIC,例如,您的编译器可能支持这些代码.然后编译器和链接器都必须做额外的工作才能使这些项独立.汇编语言程序员必须自己完成这一操作,汇编程序通常不参与其中,它只是为您指示生成的指令创建机器代码.

novectors.s:

.globl _start
_start:
    b   reset
reset:
    mov sp,#0xD8000000
    bl notmain
    ldr r0,=notmain
    blx r0
hang: b hang

.globl dummy
dummy:
    bx lr
Run Code Online (Sandbox Code Playgroud)

你好ç

extern void dummy ( unsigned int );
int notmain ( void )
{
    unsigned int ra;
    for(ra=0;ra<1000;ra++) dummy(ra);
    return(0);
}
Run Code Online (Sandbox Code Playgroud)

memap(链接描述文件)MEMORY {ram:ORIGIN = 0xD6000000,LENGTH = 0x4000} SECTIONS {.text:{ (.text)}> ram} Makefile

ARMGNU = arm-none-eabi
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding 
all : hello_world.bin
clean :
    rm -f *.o
    rm -f *.bin
    rm -f *.elf
    rm -f *.list

novectors.o : novectors.s
    $(ARMGNU)-as novectors.s -o novectors.o

hello.o : hello.c
    $(ARMGNU)-gcc $(COPS) -c hello.c -o hello.o

hello_world.bin : memmap novectors.o hello.o 
    $(ARMGNU)-ld novectors.o hello.o -T memmap -o hello_world.elf
    $(ARMGNU)-objdump -D hello_world.elf > hello_world.list
    $(ARMGNU)-objcopy hello_world.elf -O binary hello_world.bin 
Run Code Online (Sandbox Code Playgroud)

hello_world.list(我们关心的部分)

Disassembly of section .text:

d6000000 <_start>:
d6000000:   eaffffff    b   d6000004 <reset>

d6000004 <reset>:
d6000004:   e3a0d336    mov sp, #-671088640 ; 0xd8000000
d6000008:   eb000004    bl  d6000020 <notmain>
d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
d6000010:   e12fff30    blx r0

d6000014 <hang>:
d6000014:   eafffffe    b   d6000014 <hang>

d6000018 <dummy>:
d6000018:   e12fff1e    bx  lr
d600001c:   d6000020    strle   r0, [r0], -r0, lsr #32

d6000020 <notmain>:
d6000020:   e92d4010    push    {r4, lr}
d6000024:   e3a04000    mov r4, #0
d6000028:   e1a00004    mov r0, r4
d600002c:   e2844001    add r4, r4, #1
d6000030:   ebfffff8    bl  d6000018 <dummy>
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 <notmain+0x8>
d600003c:   e3a00000    mov r0, #0
d6000040:   e8bd4010    pop {r4, lr}
d6000044:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

我在这里展示的是位置无关指令和位置相关指令的混合.

例如,这两个指令是一个快捷方式,用于强制汇编程序添加.word样式的内存位置,然后链接器必须为我们填写.

ldr r0,=notmain
blx r0
Run Code Online (Sandbox Code Playgroud)

0xD600001c就是那个位置.

    d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
    d6000010:   e12fff30    blx r0
...
    d600001c:   d6000020    strle   r0, [r0], -r0, lsr #32
Run Code Online (Sandbox Code Playgroud)

并且它填充了地址0xD6000020,这是一个绝对地址,因此对于该代码工作,函数notmain必须在地址0xD6000020,它不可重定位.但是这个例子的部分也展示了一些与位置无关的代码

ldr r0, [pc, #8]
Run Code Online (Sandbox Code Playgroud)

是pc相对寻址我在谈论这个指令集工作的方式是在执行时pc是提前两个指令,或者基本上在这种情况下,如果指令在内存中的0xD600000c那么pc在执行时将是0xD6000014然后添加8作为指令说明,你得到0xD600001C.但是如果我们将完全相同的机器代码指令移动到地址0x1000 并且我们移动所有周围的二进制文件,包括它正在读取的东西(0xD6000020).基本上这样做:

    1000:   e59f0008    ldr r0, [pc, #8]    
    1004:   e12fff30    blx r0
...
    1010:   d6000020    
Run Code Online (Sandbox Code Playgroud)

而那些说明,机器代码仍然可以工作,它不需要重新组装或重新链接.0xD6000020代码应该位于固定地址位ldr pc和blx dont.

虽然反汇编程序显示了这些基于0xd6 ...的地址,但bl和bne也是pc相对的,你可以通过查看指令集文档找到它们

d6000030:   ebfffff8    bl  d6000018 <dummy>
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 <notmain+0x8>
Run Code Online (Sandbox Code Playgroud)

执行时0xD6000030的pc为0xD6000038,0xD6000038-0xD6000018 = 0x20,即8条指令.并且两个补码中的负8是0xFFF..FFFF8,你可以看到ebfffff8的大部分机器码是ffff8,这是什么是符号扩展并添加到程序计数器基本上说分支后退8个指令.同样适用于1afffffa中的ffffa,它意味着如果不相等则则向后分支6个指令.请记住,此指令集(手臂)假定PC前面有两条指令,因此后面6表示向前2表示向后6表示有效向后4表示.

如果删除了

d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
d6000010:   e12fff30    blx r0
Run Code Online (Sandbox Code Playgroud)

然后整个程序最终都是位置独立的,如果你愿意(我碰巧知道它会发生),但不是因为我告诉工具这样做,而仅仅是因为我把一切都关闭了,并没有使用任何绝对寻址.

最后当你说"链接器为他们找到空间的地方"时,如果你在我的链接器脚本中注意到我告诉链接器将所有内容都放在0xD6000000,我没有指定任何文件名或函数,所以如果没有告知,否则这个链接器会放置这些项目按照在命令行中指定的顺序.hello.c代码是第二个,所以在链接器放置novectors.s代码之后,链接器有空间的地方就在那之后,hello.c代码从0xD6000020开始.

并且一种简单的方法来查看与位置无关的内容以及不必研究每条指令的内容将是更改链接器脚本以将代码放在其他地址.

MEMORY
{
    ram : ORIGIN = 0x1000, LENGTH = 0x4000
}
SECTIONS
{
    .text : { *(.text*) } > ram
}
Run Code Online (Sandbox Code Playgroud)

并查看哪些机器代码有任何变化,以及什么不是.

00001000 <_start>:
    1000:   eaffffff    b   1004 <reset>

00001004 <reset>:
    1004:   e3a0d336    mov sp, #-671088640 ; 0xd8000000
    1008:   eb000004    bl  1020 <notmain>
    100c:   e59f0008    ldr r0, [pc, #8]    ; 101c <dummy+0x4>
    1010:   e12fff30    blx r0

00001014 <hang>:
    1014:   eafffffe    b   1014 <hang>

00001018 <dummy>:
    1018:   e12fff1e    bx  lr
    101c:   00001020    andeq   r1, r0, r0, lsr #32

00001020 <notmain>:
    1020:   e92d4010    push    {r4, lr}
    1024:   e3a04000    mov r4, #0
    1028:   e1a00004    mov r0, r4
    102c:   e2844001    add r4, r4, #1
    1030:   ebfffff8    bl  1018 <dummy>
    1034:   e3540ffa    cmp r4, #1000   ; 0x3e8
    1038:   1afffffa    bne 1028 <notmain+0x8>
    103c:   e3a00000    mov r0, #0
    1040:   e8bd4010    pop {r4, lr}
    1044:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

  • 您是否暗示Position Independent Code与Relocatable代码相同? (2认同)

The*_*der 7

我不确定此处接受的答案是否一定正确。可重定位代码与位置无关代码之间存在根本区别。

现在,我已经在很多种不同的体系结构上进行了很长一段时间的汇编代码编写,而且我一直认为机器代码具有三种特定的风格:

  • 位置无关代码
  • 可重定位代码
  • 绝对码

首先让我们讨论与位置无关的代码。这是汇编时具有相对于彼此的所有指令的代码。因此,例如分支指定与当前指令指针(或程序计数器)的偏移量。与位置无关的代码将仅包含一个代码段,并且其数据也将包含在该段(或部分)中。将数据嵌入同一段中也有例外,但这通常是操作系统或加载程序传递给您的好处。

这是一种非常有用的代码类型,因为它意味着操作系统不需要对其执行任何后加载操作即可开始执行。它只会在内存中加载的任何地方运行。当然,这种类型的代码也有其问题,即无法分离代码和数据,这些可能适合于不同的内存类型和大小限制,然后亲属开始超出范围等。

可重定位代码在许多方面都非常类似于位置无关的代码,但是却有非常细微的差别。顾名思义,这种类型的代码是可重定位的,因为可以将代码加载到内存中的任何位置,但是通常在执行之前已被重新定位或固定。实际上,一些使用这种类型代码的体系结构嵌入了诸如“ reloc”部分之类的东西,用于修复代码中可重定位的部分。这种类型的代码的缺点是,一旦重新定位并修复了它,它实际上几乎变成了绝对的并固定在其地址上。

使可重定位代码的主要优点在于,它是最流行的代码,其原因是它使代码易于分解为多个部分。每个部分都可以加载到内存中的任何位置以满足其需求,然后在重定位期间,可以使用重定位表来固定引用另一个部分的任何代码,从而可以将这些部分很好地捆绑在一起。该代码本身通常是相对的(与x86体系结构一样),但是它不一定是相对的,因为可能超出范围的任何内容都可以组装为可重定位的指令,从而使其包含添加到其加载地址的偏移量。这也意味着相对寻址所施加的限制不再是问题。

最终的代码类型是Absolute-Code。该代码被汇编为在一个特定地址工作,并且在该特定地址加载时才起作用。跳转和跳转指令都包含一个固定的精确(绝对)地址。这是嵌入式系统上通常可以找到的一种代码,因此可以确保将一段代码加载到该特定地址,因为这是唯一被加载的代码。在现代计算机上,这样的绝对代码将无法工作,因为需要在有可用内存的任何地方加载代码,并且永远无法保证一定的内存范围可用。尽管绝对代码确实有其优点,主要是因为它通常是执行速度最快的,但这可能取决于平台。


Lor*_*tel 5

实际上包含代码内部地址的任何内容都有一个绝对地址.可以从任何地址运行代码中不包含地址的程序(所有内容都使用相对地址完成).

程序员会这样做,汇编程序不会这样做.我过去做过一些,对于小东西来说通常很容易,一旦超出相对跳跃的范围就变得非常痛苦.IIRC只有两种方法是在例程之间滑动相对跳转或者向当前地址添加已知偏移量,推送它然后返回.在过去,有第三种计算方法并将其写入代码,但这已不再可接受.这已经足够长了,我不会发誓没有其他办法.

IIRC是"调用"没有绝对地址的东西的唯一方法是推送你想要返回的地址,计算地址,推送它并返回.

请注意,在实践中,您通常使用混合方法.汇编器和链接器存储进行调整所需的信息,当程序加载到内存中时,它被修改为在加载的任何地址运行.因此,内存中的实际图像是绝对的,但磁盘上的文件就像是相对的一样,但没有通常引入的所有头痛.(请注意,相同的方法与实际生成本机代码的所有更高级语言一起使用.)


Shu*_*Das 5

基本上,“绝对”模式意味着代码和 RAM 变量将准确地放置在您告诉汇编器的位置,而“可重定位”意味着汇编器构建代码块并指定可以放置在链接器找到空间的任何地方的 RAM 需求他们。