在学习汇编程序时,我遇到了这些术语。我得到的想法是这样的,在可重定位机器代码中,代码不依赖于静态RAM位置。汇编程序指定我的程序的RAM需求。内存可以放在链接器为它们找到空间的任何地方。
这个想法是否正确?如果是这样,汇编程序如何完成?
并且,绝对机器代码的一些例子是什么?
答案 0 :(得分:23)
许多/大多数指令集具有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
的hello.c
extern void dummy ( unsigned int );
int notmain ( void )
{
unsigned int ra;
for(ra=0;ra<1000;ra++) dummy(ra);
return(0);
}
memap(链接描述文件) 记忆 { ram:ORIGIN = 0xD6000000,LENGTH = 0x4000 } SECTIONS { .text:{(。text )}&gt;内存 } 生成文件
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 AND ,我们会移动所有周围的二进制文件,包括它正在读取的东西(0xD6000020)。基本上这样做:
1000: e59f0008 ldr r0, [pc, #8]
1004: e12fff30 blx r0
...
1010: d6000020
那些说明,机器代码仍然有用,它不需要重新组装或重新链接。 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>
0xD6000030在执行时会有一个0xD6000038,而0xD6000038-0xD6000018 = 0x20这是8条指令。并且两个补码中的负8是0xFFF..FFFF8,你可以看到ebfffff8的大部分机器码是ffff8,这是符号扩展并添加到程序计数器的基本上是分支后退8个指令。同样适用于1afffffa中的ffffa,它意味着如果不相等则则向后分支6个指令。请记住,这个指令集(arm)假设pc是前面的两个指令,因此back 6表示前向2然后返回6或有效地返回4。
如果删除
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
然后这整个程序最终都是独立的,如果你愿意(我碰巧知道它会发生),但不是因为我告诉工具这样做但只是因为我把一切都关闭了并且没有使用任何绝对寻址。
最后当你说&#34;链接器找到他们的空间时,#34;如果你在我的链接器脚本中注意到我告诉链接器将所有内容都放在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
答案 1 :(得分:5)
任何实际包含代码内地址的内容都有一个绝对地址。可以从任何地址运行代码中不包含地址的程序(所有内容都使用相对地址完成)。
汇编程序不会这样做,程序员会这样做。我过去做了一点,对于小东西来说,它通常很容易,一旦超出相对跳跃的范围就变得非常痛苦。 IIRC只有两种方法是在例程之间滑动相对跳转或者向当前地址添加已知偏移量,推送它然后返回。在过去,有第三种方法来计算它并将其写入代码但不再可接受。它已经足够长,我不会发誓没有其他方法。
IIRC是通往&#34;呼叫&#34;没有绝对地址的东西是推送你想要返回的地址,计算地址,推送它并返回。
请注意,在实践中,您通常使用混合方法。汇编程序和链接程序存储进行调整所需的信息,当程序加载到内存中时,它被修改为在其加载的任何地址运行。因此,内存中的实际图像是绝对的,但磁盘上的文件就像是相对的一样,但没有通常引入的所有头痛。 (请注意,相同的方法与实际生成本机代码的所有更高级语言一起使用。)
答案 2 :(得分:4)
基本上,“绝对”模式意味着代码和RAM变量将准确放置在您告诉汇编器的位置,而“可重定位”意味着汇编器构建代码块并指定可以放置在链接器的任何位置的RAM需求为他们找到了空间。
答案 3 :(得分:4)
我不确定接受的答案在这里是否正确。可重定位代码与被认为是与位置无关的代码之间存在根本区别。
现在我已经在很长一段时间内在许多不同的架构上编写程序集了,而且我一直认为机器代码有三种不同的特性: -
我们首先讨论与位置无关的代码。这是在组装时具有相对于彼此的所有指令的代码。因此,分支例如指定当前指令指针(或程序计数器,无论您想要调用它)的偏移量。与位置无关的代码仅由一段代码组成,其数据也包含在该段(或段)中。数据嵌入同一段中有例外,但这些通常是操作系统或加载程序传递给您的好处。
这是一种非常有用的代码类型,因为它意味着操作系统不需要对其执行任何后加载操作,以便能够开始执行。它只会在内存中加载的任何地方运行。当然,这种类型的代码也有它的问题,即无法分离代码和数据,这些代码和数据可能适用于不同的内存类型和大小限制,然后亲戚开始超出范围等等。仅举几例。 / p>
可重定位代码在很多方面与位置无关的代码非常相似,但它有一个非常微妙的区别。顾名思义,这种类型的代码是可重定位的,因为代码可以在内存中的任何地方加载,但通常在可执行之前进行重定位或修复。实际上,一些使用这种类型代码的体系结构嵌入了诸如“reloc”部分之类的东西,用于修复代码的可重定位部分。这种类型的代码的缺点是,一旦它被重新定位和修复,它几乎变成绝对的并固定在它的地址。
是什么赋予了可重定位代码的主要优势,以及它是最流行的代码的原因是它允许代码很容易地分解成部分。每个部分都可以加载到内存中的任何位置以满足其要求,然后在重定位期间,任何引用另一部分的代码都可以使用重定位表进行修复,因此可以很好地将这些部分绑定在一起。代码本身通常是相对的(与x86架构一样),但它不一定是,因为任何可能超出范围的东西都可以组装成可重定位的指令,使得它包含一个添加到其加载地址的偏移量。这也意味着相对寻址所施加的限制不再是一个问题。
最终的代码类型是绝对代码。此代码汇编为在一个特定地址处工作,并且只在 加载到该特定地址时才能工作。分支和跳转指令都包含固定的精确(绝对)地址。它是嵌入式系统中常见的一种代码,可以保证在该特定地址加载一段代码,因为它是唯一加载的代码。在现代计算机上,这样的绝对代码不起作用,因为代码需要在有空闲内存的地方加载,并且永远不能保证某个内存范围可用。绝对代码确实有它的优点,主要是它通常是最快的执行,但这可能是平台相关的。
答案 4 :(得分:1)
“relocatable”表示汇编程序构建代码块并指定可以放置在链接器为它们找到空间的位置的RAM需求。