18

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

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

而且,绝对机器码的一些例子是什么?

4

4 回答 4

34

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

Absolute 使用绝对地址,跳转到这个确切地址,从这个确切地址读取。如果相等则跳转到 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

你好ç

extern void dummy ( unsigned int );
int notmain ( void )
{
    unsigned int ra;
    for(ra=0;ra<1000;ra++) dummy(ra);
    return(0);
}

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 

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

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

例如,这两条指令是强制汇编器添加 .word 样式内存位置的快捷方式,然后链接器必须为我们填充该位置。

ldr r0,=notmain
blx r0

0xD600001c 就是那个位置。

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

它用地址 0xD6000020 填充,这是一个绝对地址,因此要使该代码正常工作,函数 notmain 必须位于地址 0xD6000020,它是不可重定位的。但是示例的这一部分也演示了一些与位置无关的代码,

ldr r0, [pc, #8]

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

    1000:   e59f0008    ldr r0, [pc, #8]    
    1004:   e12fff30    blx r0
...
    1010:   d6000020    

而那些指令,机器代码仍然可以工作,它不需要重新组装或重新链接。0xD6000020 代码必须位于 ldr pc 的固定地址位,而 blx 则没有。

尽管反汇编程序使用 0xd6... 显示这些地址,但 bl 和 bne 也是 pc 相关的,您可以通过查看指令集文档找到

d6000030:   ebfffff8    bl  d6000018 <dummy>
d6000034:   e3540ffa    cmp r4, #1000   ; 0x3e8
d6000038:   1afffffa    bne d6000028 <notmain+0x8>

0xD6000030 在执行时将有一个 0xD6000038 的 pc,而 0xD6000038-0xD6000018 = 0x20 是 8 条指令。二进制补码中的负 8 是 0xFFF..FFFF8,您可以看到大部分机器代码 ebfffff8 是 ffff8,这是符号扩展并添加到程序计数器的内容,基本上说向后分支 8 个指令。1afffffa 中的 ffffa 也是如此,这意味着如果不相等,则向后分支 6 条指令。请记住,此指令集(arm)假设 pc 前面有两条指令,因此后退 6 意味着前进两条然后后退 6 或实际上后退 4。

如果您删除

d600000c:   e59f0008    ldr r0, [pc, #8]    ; d600001c <dummy+0x4>
d6000010:   e12fff30    blx r0

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

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

无需研究每条指令即可查看与位置无关和不与位置无关的简单方法是更改​​链接描述文件以将代码放在其他地址。

MEMORY
{
    ram : ORIGIN = 0x1000, LENGTH = 0x4000
}
SECTIONS
{
    .text : { *(.text*) } > ram
}

并查看哪些机器代码更改(如果有),哪些没有更改。

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
于 2014-04-06T05:39:20.863 回答
14

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

现在我已经在许多不同的架构上编写汇编代码很长时间了,我一直认为机器代码有三种特定的风格:-

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

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

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

Relocatable-Code在很多方面都非常类似于位置无关代码,但它有一个非常细微的区别。顾名思义,这种类型的代码是可重定位的,因为该代码可以加载到内存中的任何位置,但通常在可执行之前已被重定位或修复。事实上,一些使用这种类型代码的架构嵌入了诸如“reloc”部分之类的东西,目的就是为了修复代码的可重定位部分。这种类型的代码的缺点是,一旦它被重新定位和修复,它几乎在本质上变得绝对并且固定在它的地址上。

是什么赋予了可重定位代码它的主要优势以及它成为最流行代码的原因是它允许将代码轻松分解为多个部分。每个部分都可以加载到内存中的任何位置以满足其要求,然后在重定位期间,引用另一个部分的任何代码都可以使用重定位表进行修复,因此这些部分可以很好地绑定在一起。代码本身通常是相对的(与 x86 体系结构一样),但不一定是相对的,因为任何可能超出范围的内容都可以组装为可重定位指令,这样它就包含添加到其加载地址的偏移量。这也意味着相对寻址施加的限制不再是问题。

最后一种代码是Absolute-Code。此代码被组装为在一个特定地址工作,并且在加载到该特定地址时才能工作。分支和跳转指令都包含一个固定的精确(绝对)地址。它是一种通常在嵌入式系统上找到的代码,可以保证在该特定地址加载一段代码,因为它是唯一加载在那里的东西。在现代计算机上,这样的绝对代码是行不通的,因为代码需要在任何有空闲内存的地方加载,并且永远无法保证一定的内存范围可用。绝对代码确实有其优势,主要是它通常是最快的执行,但这可能取决于平台。

于 2017-09-07T16:57:30.130 回答
7

任何在代码中实际包含地址的东西都有一个绝对地址。代码中不包含地址的程序(一切都使用相对地址完成)可以从任何地址运行。

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

IIRC“调用”没有绝对地址的东西的唯一方法是推送要返回的地址,计算地址,推送并返回。

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

于 2014-04-06T04:53:37.117 回答
5

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

于 2014-11-23T10:09:24.973 回答