特定的操作系统有一组特定的规则,或者可能有多组规则,用于加载兼容程序。为该平台制作的包含默认链接器脚本(想想 gcc hello.c -o hello)的工具链符合这些规则。
例如,我决定为具有 MMU 的平台创建一个操作系统。因为它有一个 MMU,所以我可以创建操作系统,以便每个程序都看到相同的(虚拟)地址空间。所以我可以决定,对于我的操作系统上的应用程序,内存空间从 0x00000000 开始,但入口点必须是 0x00001000。可以说,支持的二进制文件格式是摩托罗拉 s-record。
所以用一个简单的链接器脚本编写一个简单的程序
MEMORY
{
ram : ORIGIN = 0x1000, LENGTH = 0x10000
}
SECTIONS
{
.text : { *(.text*) } > ram
}
我的简单程序的反汇编
00001000 <_start>:
1000: e3a0d902 mov sp, #32768 ; 0x8000
1004: eb000001 bl 1010 <main>
1008: e3a00000 mov r0, #0
100c: ef000000 svc 0x00000000
00001010 <main>:
1010: e3a00000 mov r0, #0
1014: e12fff1e bx lr
“二进制”文件恰好是人类可读的:
S00F00006E6F746D61696E2E737265631F
S3150000100002D9A0E3010000EB0000A0E3000000EF1E
S30D000010100000A0E31EFF2FE122
S70500001000EA
您可能会或可能不会注意到地址确实在描述事物去向的二进制文件中。
作为一个加载到内存中的基于操作系统的程序,我们不必玩太多的内存游戏,我们可以假设一个平坦的全内存(读/写),所以如果有 .data、.bss 等,它可以都装在里面。
对于真正的操作系统,希望二进制文件包含附加信息,可能是程序的大小。因此,您可以搜索各种常见的文件格式,看看这是如何完成的,要么是我非常需要的一个简单的预先设置,要么是单独定义的一对多部分。是的,“二进制”不仅仅是操作码和数据,我假设你明白这一点。
我使用的工具链默认输出 elf 格式的文件,但 objcopy 可用于创建许多不同的格式,其中一种是原始内存映像(不包含任何地址/位置信息),其余大部分/大部分包含机器代码和数据以及调试器/反汇编器的标签或该数据块希望在内存空间中的地址等。
现在,当您说嵌入式并使用 ROM 和 RAM 这两个词时,我假设您指的是诸如微控制器之类的裸机,但即使您指的是引导 x86 或全尺寸 ARM 或任何相同的东西。在 MCU 的情况下,芯片设计人员可能已经根据处理器的规则或他们自己的选择确定了内存空间的规则。就像操作系统会规定规则一样。我们有点作弊,因为我们今天使用的许多工具(基于 gnu)并不是真正为裸机设计的,而是因为通用编译器是通用编译器,更重要的是工具链适合这种可移植性,我们可以使用这样的工具。理想情况下,使用交叉编译器意味着输出机器代码不一定要在生成该输出机器代码的计算机上运行。重要的主要区别是我们要控制链接和库,不要链接基于主机操作系统的库,让我们控制或为此工具链有一个针对我们的 MCU 的默认链接器脚本。因此,假设我有一个基于 ARM7TDMI 的 MCU,芯片设计人员说我需要二进制文件,以便 ROM 从地址 0x00000000 开始并且具有一定大小,而 RAM 从 0x40000000 开始并且具有一定大小。作为 ARM7,处理器通过获取地址 0x00000000 处的指令开始执行,并且芯片设计人员已将该 0x00000000 映射到 ROM。
现在我的简单程序
unsigned int xyz;
int notmain ( void )
{
xyz=5;
return(0);
}
这样链接
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.bss : { *(.bss*) } > ted
}
对此进行反汇编
Disassembly of section .text:
00000000 <_start>:
0: e3a0d101 mov sp, #1073741824 ; 0x40000000
4: e38dda01 orr sp, sp, #4096 ; 0x1000
8: eb000000 bl 10 <notmain>
c: eafffffe b c <_start+0xc>
00000010 <notmain>:
10: e3a02005 mov r2, #5
14: e59f3008 ldr r3, [pc, #8] ; 24 <notmain+0x14>
18: e3a00000 mov r0, #0
1c: e5832000 str r2, [r3]
20: e12fff1e bx lr
24: 40000000 andmi r0, r0, r0
Disassembly of section .bss:
40000000 <xyz>:
40000000: 00000000 andeq r0, r0, r0
那将是一个完全有效的程序,没有什么有趣的,但仍然是一个完全有效的程序。
首先,如果您忽略 _start,工具链会发出警告,但仍然可以正常运行。 (嗯,那个时候居然没提醒,有意思)
arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy --srec-forceS3 notmain.elf -O srec notmain.srec
arm-none-eabi-objcopy notmain.elf -O binary notmain.bin
现在您遇到了加载问题。每个 MCU 在如何加载可用工具和/或制作自己的工具方面都不同。 Ihex 和 srec 在舞会程序员中很受欢迎,您曾说过处理器旁边有一个单独的 rom 和/或通孔 mcu 将插入舞会程序员。原始二进制图像也可以工作,但很快就会变大,稍后会显示。如上所述,有 .bss 但没有 .data 所以
ls -al notmain.bin
-rwxr-xr-x 1 user user 40 Oct 21 22:05 notmain.bin
40 字节。但是,如果我出于演示目的这样做,即使它无法正常工作:
unsigned int xyz=5;
int notmain ( void )
{
return(0);
}
与
MEMORY
{
bob : ORIGIN = 0x00000000, LENGTH = 0x1000
ted : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.bss : { *(.bss*) } > ted
.data : { *(.data*) } > ted
}
给予
Disassembly of section .text:
00000000 <notmain-0x10>:
0: e3a0d101 mov sp, #1073741824 ; 0x40000000
4: e38dda01 orr sp, sp, #4096 ; 0x1000
8: eb000000 bl 10 <notmain>
c: eafffffe b c <notmain-0x4>
00000010 <notmain>:
10: e3a00000 mov r0, #0
14: e12fff1e bx lr
Disassembly of section .data:
40000000 <xyz>:
40000000: 00000005 andeq r0, r0, r5
和
-rwxr-xr-x 1 user user 1073741828 Oct 21 22:08 notmain.bin
哎哟! 0x40000004 字节,这是预期的,我要求一个内存映像,我在一个地址(机器代码)定义了一些东西,在另一个地址(0x40000000)定义了几个字节,所以原始内存映像必须是整个范围。
hexdump notmain.bin
0000000 d101 e3a0 da01 e38d 0000 eb00 fffe eaff
0000010 0000 e3a0 ff1e e12f 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0000
*
40000000 0005 0000
40000004
相反,人们将只使用工具链生成的 elf 文件,或者可能是 ihex 或 srecord。
S00F00006E6F746D61696E2E737265631F
S3150000000001D1A0E301DA8DE3000000EBFEFFFFEA79
S30D000000100000A0E31EFF2FE132
S3094000000005000000B1
S70500000000FA
我需要的所有信息,但不是这么少字节的大文件。
这不是一个硬性规定,但今天移动数据更容易(比使用舞会程序员的软盘从一台计算机到另一台计算机)。特别是如果您有一个捆绑的 IDE,供应商可能使用工具链默认格式,但即使不支持 elf 和其他类似格式,您也不必走原始二进制文件或 ihex 或 srec 的路线。但它仍然取决于获取“二进制”并将其编程到 MCU 上的 ROM(/FLASH)中的工具。
现在我作弊来演示上面的大文件问题,相反,当它不是一个仅 ram 的系统时,你必须做更多的工作。如果您觉得需要 .data 或希望将 .bss 归零,那么您需要编写或使用更复杂的链接器脚本来帮助您确定位置和边界。并且该链接器脚本与使用链接器生成的信息来执行这些任务的引导程序相结合。基本上 .data 的副本需要保存在非易失性内存 (ROM/FLASH) 中,但它不能在运行时保存在那里 .data 是读/写的,因此理想情况下/通常您使用链接器脚本语言/魔术来声明 .数据读/写空间是blah,闪存空间在这个地址和这个大小是boo,所以引导程序可以从那个地址的闪存复制那个数量的数据到ram。对于 .bss,链接器脚本会生成我们保存到闪存中的变量,这些变量告诉引导程序从这个地址到这个地址将 ram 归零。
所以操作系统定义了内存空间,如果你想让程序工作,链接描述文件就会匹配。系统设计人员或芯片设计人员确定嵌入的地址空间,并且链接描述文件与之匹配。引导程序与该构建和目标的链接描述文件结合。
编辑 -------------
工具链基础...
mov sp,#0x40000000
orr sp,sp,#0x1000
bl notmain
b .
unsigned int xyz;
int notmain ( void )
{
xyz=5;
return(0);
}
MEMORY
{
bob : ORIGIN = 0x1000, LENGTH = 0x1000
ted : ORIGIN = 0x2000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > bob
.bss : { *(.bss*) } > ted
}
我的引导程序、主程序和链接器脚本
arm-none-eabi-as --warn --fatal-warnings vectors.s -o vectors.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding -save-temps -c notmain.c -o notmain.o
arm-none-eabi-ld vectors.o notmain.o -T memmap -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy --srec-forceS3 notmain.elf -O srec notmain.srec
arm-none-eabi-objcopy notmain.elf -O binary notmain.bin
有些人会争论,有时确实编译不再生成程序集。仍然是明智的做法,您会经常发现它,就像在这种情况下......
bootstrap 生成了一个我们可以反汇编的对象。
00000000 <.text>:
0: e3a0d101 mov sp, #1073741824 ; 0x40000000
4: e38dda01 orr sp, sp, #4096 ; 0x1000
8: ebfffffe bl 0 <notmain>
c: eafffffe b c <.text+0xc>
它没有“链接”,所以这个反汇编器使用的地址是从零开始的,你可以看到对 notmain 的调用是不完整的,还没有链接。
编译器为 C 代码生成程序集
.cpu arm7tdmi
.fpu softvfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 1
.eabi_attribute 30, 2
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "notmain.c"
.text
.align 2
.global notmain
.type notmain, %function
notmain:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
mov r2, #5
ldr r3, .L2
mov r0, #0
str r2, [r3]
bx lr
.L3:
.align 2
.L2:
.word xyz
.size notmain, .-notmain
.comm xyz,4,4
.ident "GCC: (15:4.9.3+svn231177-1) 4.9.3 20150529 (prerelease)"
组装成一个我们也可以拆卸的对象。
Disassembly of section .text:
00000000 <notmain>:
0: e3a02005 mov r2, #5
4: e59f3008 ldr r3, [pc, #8] ; 14 <notmain+0x14>
8: e3a00000 mov r0, #0
c: e5832000 str r2, [r3]
10: e12fff1e bx lr
14: 00000000 andeq r0, r0, r0
现在未显示,但该对象还包含全局变量 xyz 及其大小的信息。
链接器工作可能是您困惑的一部分。它将对象链接在一起,这样结果将是理智的或将在最终目的地(裸机或操作系统)上工作。
Disassembly of section .text:
00001000 <notmain-0x10>:
1000: e3a0d101 mov sp, #1073741824 ; 0x40000000
1004: e38dda01 orr sp, sp, #4096 ; 0x1000
1008: eb000000 bl 1010 <notmain>
100c: eafffffe b 100c <notmain-0x4>
00001010 <notmain>:
1010: e3a02005 mov r2, #5
1014: e59f3008 ldr r3, [pc, #8] ; 1024 <notmain+0x14>
1018: e3a00000 mov r0, #0
101c: e5832000 str r2, [r3]
1020: e12fff1e bx lr
1024: 00002000 andeq r2, r0, r0
Disassembly of section .bss:
00002000 <xyz>:
2000: 00000000 andeq r0, r0, r0
我制作了这个链接器脚本,以便您可以看到 .data 和 .bss 的移动。链接器已将所有 .text 填充到 0x1000 地址空间中,并修补了对 notmain() 的调用以及如何到达 xyz。它还在 0x2000 地址空间中为 xyz 变量分配/定义了空间。
然后是您的下一个问题或困惑。这很大程度上取决于加载系统的工具,无论是操作系统将程序加载到内存中以运行,还是对 MCU 的闪存进行编程或对其他嵌入式系统的 ram 进行编程(例如鼠标您可能不知道其中一些固件是从操作系统下载的,而不是所有固件都烧录到闪存 /lib/firmware 或其他位置)。