在学习汇编语言时,我遇到了这些术语。我的理解是,在可重定位的机器代码中,代码不依赖于静态RAM位置。汇编器为程序指定RAM需求。内存可以放置在链接器找到的任何空间。
这个理念正确吗?如果是,汇编器是如何实现的?
还有,一些绝对机器代码的例子是什么?
在学习汇编语言时,我遇到了这些术语。我的理解是,在可重定位的机器代码中,代码不依赖于静态RAM位置。汇编器为程序指定RAM需求。内存可以放置在链接器找到的任何空间。
这个理念正确吗?如果是,汇编器是如何实现的?
还有,一些绝对机器代码的例子是什么?
.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 } 节 { .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
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指向当前PC的两个指令(即PC+8),因此,在内存中如果指令位于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>
d600000c: e59f0008 ldr r0, [pc, #8] ; d600001c <dummy+0x4>
d6000010: e12fff30 blx r0
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
我不确定被接受的答案在这里是否正确。Relocatable Code 和Position-Independent Code之间有根本的差别。
现在,我已经编写汇编代码很长时间,使用过许多不同的架构,并且一直认为机器代码有三种特定的风味:
首先让我们讨论position-independent code。这是一种当汇编后所有指令相对于其他指令的代码。例如,分支指令会从当前指针(或程序计数器,无论您想称之为哪个)指定一个偏移量。位置无关的代码将仅由一个代码段组成,并且其数据也包含在此段(或部分)中。数据嵌入在同一段中存在例外情况,但这些通常是由操作系统或加载器向您传递的好处。
这是一种非常有用的代码类型,因为它意味着操作系统不需要执行任何后加载操作即可开始执行。它将在内存中加载的任何地方运行。当然,这种类型的代码也有其问题,包括不能将可能适合于不同内存类型和限制的代码和数据隔离以及在相关性超出范围之前的大小限制等问题。
Relocatable-Code 在许多方面与 position-independent code 相似,但它有一个非常微妙的区别。正如其名称所示,这种类型的代码是可重定位的,因为代码可以在内存中加载到任何位置,但通常必须在可执行之前进行重新定位或修正。实际上,某些使用这种类型代码的架构针对此目的嵌入了“reloc”部分,以修复代码的可重定位部分。这种类型代码的缺点是,一旦它被重新定位并修正,它几乎变成绝对的,并且固定在其地址上。
所谓可重定位代码的主要优势,也是其成为最常见代码的原因,是它允许将代码轻松分解成部分。每个部分可以在内存中的任何位置加载以适应其要求,然后在重新定位期间,任何引用其他部分的代码都可以使用一个重定位表进行修正,从而将各个部分紧密地连接在一起。代码本身通常是相对的(如x86架构),但不必如此,因为任何可能超出范围的内容都可以被组装为可重定向指令,即由偏移量加上其加载地址构成。这还意味着相对寻址所施加的限制已经不再是问题。实际上在代码中包含地址的内容,称作绝对地址。完全使用相对地址的程序(所有操作都使用相对地址完成)可以从任何地址运行。
汇编器并不会这样做,这是由程序员来完成的。我曾经为小程序做过一些操作,通常而言,这是很容易的,但是一旦超出了相对跳转的范围,则变得非常麻烦。如果我没记错的话,唯一的两种方法要么是在例程之间插入相对跳转,要么就是添加已知偏移量到当前地址,将其推入堆栈,然后返回。在早期有第三种计算和写入代码的方法,但现在不再被接受。我已经忘得够久了,所以也不能保证没有其他方法。
如果没有绝对地址,唯一的“调用”方式就是先将想要返回的地址推入堆栈,然后计算地址,将其推入堆栈,最后返回。
需要注意的是,在实际操作中,通常采用混合方案。汇编器和链接器存储了需要进行调整的信息,当程序被加载到内存中时,它会被修改为适应加载的地址。因此,内存中的实际图像是绝对的,但磁盘上的文件看起来像是相对的,但不会引入通常会带来麻烦的问题。(需要注意的是,所有实际生成本机代码的高级语言都使用相同的方法。)
基本上,“绝对”模式意味着代码和RAM变量将被放置在你告诉汇编器的确切位置,而“可重定位”模式意味着汇编器构建代码块并指定可以放置在链接器找到空间的RAM需求。