裸机编程 Raspberry Pi 3.

Bare metal programming Raspberry Pi 3.

我正在学习一些裸机编程教程。在阅读 C 代码执行时,我开始知道我们需要设置 C 执行环境,如初始化堆栈归零 bss 等。

在某些情况下,您必须在 ram 中复制数据,并且还需要为此提供启动代码。 Link 的教程,它说在 RAM 中复制数据。 现在有两个疑惑。

如果我们需要在 RAM 中复制数据,那么我们为什么不复制代码,即文本段。如果我们不复制文本段是否意味着在 Raspberry pi 3(Arm 嵌入式处理器)的情况下代码是从 SD 卡本身执行的。

当我们像下面这样指定链接描述文件时,是否建议将这些部分复制到 RAM 中,或者这些部分将映射到 RAM 地址中? 对不起,我真的很困惑。

MEMORY
{
   ram : ORIGIN = 0x8000, LENGTH = 0x1000
}

SECTIONS
{
   .text : { *(.text*) } > ram
   .bss : { *(.bss*) } > ram
}

感谢任何帮助。

vectors.s

.globl _start
_start:
    mov sp,#0x8000
    bl notmain
    b .

notmain.c

unsigned int x;
unsigned int y=0x12345678;

void notmain ( void )
{
    x=y+7;
}

内存映射

MEMORY
{
    bob : ORIGIN = 0x80000000, LENGTH = 0x1000
    ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ted
    .rodata : { *(.rodata*) } > ted
    .bss : { *(.bss*) } > ted
    .data : { *(.data*) } > ted
}

建设

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 notmain.elf -O binary kernel.img

你可以 add/remove 选项,并将其命名为正确的 kernelX.img (如果你打算进入 64 位,那么使用 aarch64-whatever-gcc 而不是 arm-whatever-gcc ...

看反汇编

Disassembly of section .text:

00008000 <_start>:
    8000:   e3a0d902    mov sp, #32768  ; 0x8000
    8004:   eb000000    bl  800c <notmain>
    8008:   eafffffe    b   8008 <_start+0x8>

0000800c <notmain>:
    800c:   e59f3010    ldr r3, [pc, #16]   ; 8024 <notmain+0x18>
    8010:   e5933000    ldr r3, [r3]
    8014:   e59f200c    ldr r2, [pc, #12]   ; 8028 <notmain+0x1c>
    8018:   e2833007    add r3, r3, #7
    801c:   e5823000    str r3, [r2]
    8020:   e12fff1e    bx  lr
    8024:   00008030    andeq   r8, r0, r0, lsr r0
    8028:   0000802c    andeq   r8, r0, r12, lsr #32

Disassembly of section .bss:

0000802c <x>:
    802c:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

00008030 <y>:
    8030:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

并将其与 kernelX.img 文件进行比较

hexdump -C kernel.img 
00000000  02 d9 a0 e3 00 00 00 eb  fe ff ff ea 10 30 9f e5  |.............0..|
00000010  00 30 93 e5 0c 20 9f e5  07 30 83 e2 00 30 82 e5  |.0... ...0...0..|
00000020  1e ff 2f e1 30 80 00 00  2c 80 00 00 00 00 00 00  |../.0...,.......|
00000030  78 56 34 12                                       |xV4.|
00000034

请注意,因为我在 linker 脚本中将 .data 放在 .bss 之后,所以它在图像中按该顺序放置了它们。 .text 的最后一个字和 .data

的 0x12345678 之后有四个字节的零

如果在 linker 脚本中交换 .bss 和 .data 的位置

0000802c <y>:
    802c:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

Disassembly of section .bss:

00008030 <x>:
    8030:   00000000    andeq   r0, r0, r0

00000000  02 d9 a0 e3 00 00 00 eb  fe ff ff ea 10 30 9f e5  |.............0..|
00000010  00 30 93 e5 0c 20 9f e5  07 30 83 e2 00 30 82 e5  |.0... ...0...0..|
00000020  1e ff 2f e1 2c 80 00 00  30 80 00 00 78 56 34 12  |../.,...0...xV4.|
00000030

糟糕,没有免费赠品。现在 .bss 没有归零,你需要在你的 bootstrap 中将它归零(如果你有一个 .bss 区域并且作为一种编程风格,你假设这些项目在你第一次使用它们时为零)。

好的,那么你如何找到 .bss 在哪里?好吧,这就是本教程和无数其他教程向您展示的内容。

.globl _start
_start:
    mov sp,#0x8000
    bl notmain
    b .
linker_stuff:
.word hello_world
.word world_hello

MEMORY
{
    bob : ORIGIN = 0x80000000, LENGTH = 0x1000
    ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ted
    .rodata : { *(.rodata*) } > ted
    .data : { *(.data*) } > ted

    hello_world = .;
    .bss : { *(.bss*) } > ted
    world_hello = .;
}

构建和反汇编

Disassembly of section .text:

00008000 <_start>:
    8000:   e3a0d902    mov sp, #32768  ; 0x8000
    8004:   eb000002    bl  8014 <notmain>
    8008:   eafffffe    b   8008 <_start+0x8>

0000800c <linker_stuff>:
    800c:   00008038    andeq   r8, r0, r8, lsr r0
    8010:   0000803c    andeq   r8, r0, r12, lsr r0

00008014 <notmain>:
    8014:   e59f3010    ldr r3, [pc, #16]   ; 802c <notmain+0x18>
    8018:   e5933000    ldr r3, [r3]
    801c:   e59f200c    ldr r2, [pc, #12]   ; 8030 <notmain+0x1c>
    8020:   e2833007    add r3, r3, #7
    8024:   e5823000    str r3, [r2]
    8028:   e12fff1e    bx  lr
    802c:   00008034    andeq   r8, r0, r4, lsr r0
    8030:   00008038    andeq   r8, r0, r8, lsr r0

Disassembly of section .data:

00008034 <y>:
    8034:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

Disassembly of section .bss:

00008038 <x>:
    8038:   00000000    andeq   r0, r0, r0

因此,深入挖掘特定于工具链的内容,我们现在可以知道 .bss 的开始和结束,或者可以在 linker 脚本中使用数学来获取大小和长度。从中您可以编写一个小循环来清零该内存(当然,在汇编语言中,先有鸡还是先有蛋,在跳转到程序的 C 入口点之前的 bootstrap)。

现在说出于某种原因你想要 .data 在其他地址 0x10000000

.globl _start
_start:
    mov sp,#0x8000
    bl notmain
    b .

MEMORY
{
    bob : ORIGIN = 0x10000000, LENGTH = 0x1000
    ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ted
    .rodata : { *(.rodata*) } > ted
    .bss : { *(.bss*) } > ted
    .data : { *(.data*) } > bob
}

00008000 <_start>:
    8000:   e3a0d902    mov sp, #32768  ; 0x8000
    8004:   eb000000    bl  800c <notmain>
    8008:   eafffffe    b   8008 <_start+0x8>

0000800c <notmain>:
    800c:   e59f3010    ldr r3, [pc, #16]   ; 8024 <notmain+0x18>
    8010:   e5933000    ldr r3, [r3]
    8014:   e59f200c    ldr r2, [pc, #12]   ; 8028 <notmain+0x1c>
    8018:   e2833007    add r3, r3, #7
    801c:   e5823000    str r3, [r2]
    8020:   e12fff1e    bx  lr
    8024:   10000000    andne   r0, r0, r0
    8028:   0000802c    andeq   r8, r0, r12, lsr #32

Disassembly of section .bss:

0000802c <x>:
    802c:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

10000000 <y>:
10000000:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

那么 kernel.img 或 -O 二进制格式是什么?它只是一个从最低地址(在本例中为 0x8000)开始并填充或填充到最高地址(在本例中为 0x10000003)的内存映像,因此它是一个 0x10000004-0x8000 字节文件。

00000000  02 d9 a0 e3 00 00 00 eb  fe ff ff ea 10 30 9f e5  |.............0..|
00000010  00 30 93 e5 0c 20 9f e5  07 30 83 e2 00 30 82 e5  |.0... ...0...0..|
00000020  1e ff 2f e1 00 00 00 10  2c 80 00 00 00 00 00 00  |../.....,.......|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
0fff8000  78 56 34 12                                       |xV4.|
0fff8004

对于这个程序来说,这是对磁盘的巨大浪费 space,他们把它填满了。现在,如果出于某种原因你想做这样的事情,各种原因(通常不适用于 pi 上的裸机),你可以改为这样做:

MEMORY
{
    bob : ORIGIN = 0x10000000, LENGTH = 0x1000
    ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ted
    .rodata : { *(.rodata*) } > ted
    .bss : { *(.bss*) } > ted
    .data : { *(.data*) } > bob AT > ted
}



00000000  02 d9 a0 e3 00 00 00 eb  fe ff ff ea 10 30 9f e5  |.............0..|
00000010  00 30 93 e5 0c 20 9f e5  07 30 83 e2 00 30 82 e5  |.0... ...0...0..|
00000020  1e ff 2f e1 00 00 00 10  2c 80 00 00 00 00 00 00  |../.....,.......|
00000030  78 56 34 12                                       |xV4.|
00000034

Disassembly of section .bss:

0000802c <x>:
    802c:   00000000    andeq   r0, r0, r0

Disassembly of section .data:

10000000 <y>:
10000000:   12345678    eorsne  r5, r4, #120, 12    ; 0x7800000

它所做的是编译代码并 linked 用于 0x10000000 处的 .data 但您随身携带和加载的二进制文件将 .data 数据捆绑在一起,这是bootstrap 将该数据复制到其正确的着陆点 0x10000000 并且您必须再次使用特定于工具链的 linker scripty stuff

.globl _start
_start:
    mov sp,#0x8000
    bl notmain
    b .

linker_stuff:
.word data_start
.word data_end

MEMORY
{
    bob : ORIGIN = 0x10000000, LENGTH = 0x1000
    ted : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ted
    .rodata : { *(.rodata*) } > ted
    .bss : { *(.bss*) } > ted
    data_start = .;
    .data : { *(.data*) } > bob AT > ted
    data_end = .;
}

0000800c <linker_stuff>:
    800c:   00008038    andeq   r8, r0, r8, lsr r0
    8010:   10000004    andne   r0, r0, r4

很明显,这不太奏效,所以你必须做更多 linker scripy 的东西来弄清楚。

没有充分的理由为 raspberry pi 需要任何这些,如果您有 .bss 而没有任何 .data and/or 最好将 .bss 放在最后,如果您有很多,那么您可以利用工具链意外地补零并为您解决 .bss 问题,或者如果二进制文件太大,那么您可以在上面看到如何找到 .bss 偏移量和大小然后添加只需几行代码即可将其归零(无论哪种方式最终都会花费加载时间,但不会花费 SD 卡 space)。

当你使用非易失性被视为只读闪存的微控制器时,如果你选择使用需要 .data 的样式进行编程,那么你肯定需要学习这些东西 and/or .bss 并且您假设这些项目已实现,那么您必须对 link 执行工具链特定工作,然后将零 and/or 从非易失性闪存复制到 read/write ram,然后再分支到第一个或者您的应用程序的唯一 C 入口点。

我敢肯定有人会提出不将 pi 裸机二进制文件打包得很好和整洁的理由,总有例外......但现在你不需要担心这些例外,把 .bss首先,然后是 .data,并始终确保您有一个 .data 项目,即使它是您从未使用过的东西。