2

使用时我的行为有所不同

arm-none-eabi-ld -T t.ld -o t.elf t.o ts.o

链接我的目标文件,vs

arm-none-eabi-ld -T t.ld -o t.elf ts.o t.o

其中目标文件“to”和“ts.o”在命令中被转置。后一个版本会产生正确的行为,而前一个版本不会。不同之处似乎是我的程序中的堆栈指针在第一个版本中设置不正确,我想知道为什么会这样。

这是我正在使用的源文件和链接器脚本,以及要编译的脚本。

tld

ENTRY(start) /* define start as the entry address */
SECTIONS
{
    . = 0x10000; /* loading address, required by QEMU */
    .text : { *(.text) }
    .data : { *(.data) }
    .bss : { *(.bss) }
    . =ALIGN(8);
        . =. + 0x1000;
    stack_top =.;
}

tc

int g = 100; // un-initialized global

extern int sum(int a, int b, int c, int d, int e, int f);

int main() {
    int a, b, c, d, e, f; // local variables
    a = b = c = d = e = f = 1; // values do not matter
    g = sum(a, b, c, d, e, f); // call sum()
}

ts.s

/*
    Assembly file to define sum()
 */
    .global start, sum
start:
    ldr sp, =stack_top // set sp to stack top
    bl main // call main()

stop: b stop // loop

sum:
    // establish stack frame
    stmfd sp!, {fp, lr} // push lr and fp
    add fp, sp, #4 // fp -> saved lr on stack
    // compute sum of all 6 parameters
    add r0, r0, r1 // r0 = a + b
    add r0, r0, r2 // r0 = a + b + c
    add r0, r0, r3 // r0 = a + b + c + d
    ldr r3, [fp, #4] // r1 = e
    add r0, r0, r3 // r0 = a + b + c + d + e
    ldr r3, [fp, #8] // r1 = f
    add r0, r0, r3 // r0 = a + b + c + d + e + f
    // return
    sub sp, fp, #4 // point stack pointer to saved fp
    ldmfd sp!, {fp, pc} // return to caller

mk.sh(使用产生预期结果的链接器命令)

arm-none-eabi-as -o ts.o ts.s # assemble ts.s
arm-none-eabi-gcc -c t.c # cross-compile t.c into t.o
arm-none-eabi-ld -T t.ld -o t.elf ts.o t.o # link object files into t.elf
arm-none-eabi-objcopy -O binary t.elf t.bin # convert t.elf to t.bin

运行二进制文件后

qemu-system-arm -M versatilepb -kernel t.bin -nographic -serial /dev/null

我得到以下。堆栈指针 (R13) 正确

(qemu) info registers
R00=00000000 R01=00000001 R02=000100c0 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=000110c8 R14=00010008 R15=00010008
PSR=400001d3 -Z-- A svc32
FPSCR: 00000000

VS 使用带有转置目标文件的链接器命令的结果

(qemu) info registers
R00=00000000 R01=00000183 R02=00000100 R03=00000000
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=f3575ee4
R12=00000000 R13=f3575ec0 R14=00010060 R15=00010000
PSR=400001d3 -Z-- A svc32
FPSCR: 00000000

堆栈指针(R13)明显超出程序的内存范围。

4

1 回答 1

0

更简单:

闪存.s

.global _start
_start:
    ldr sp,=0x11000
    bl main
    b .

闪存.ld

ENTRY(_start)

MEMORY
{
    ram : ORIGIN = 0x10000, LENGTH = 0x1000
}
SECTIONS
{
    .text   : { *(.text*)   } > ram
    .rodata : { *(.rodata*) } > ram
    .bss    : { *(.bss*)    } > ram
    .data   : { *(.data*)   } > ram
}

so.c

int  main ( void )
{   
    return 5;
}

建造

arm-none-eabi-as --warn --fatal-warnings  flash.s -o flash.o
arm-none-eabi-gcc -c -Wall -O2 -ffreestanding  so.c -o so.o
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld flash.o so.o -o one.elf
arm-none-eabi-objdump -D one.elf > one.list
arm-none-eabi-objcopy -O binary one.elf one.bin
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld so.o flash.o -o two.elf
arm-none-eabi-objdump -D two.elf > two.list
arm-none-eabi-objcopy -O binary two.elf two.bin

检查:

one.elf:     file format elf32-littlearm


Disassembly of section .text:

00010000 <_start>:
   10000:   e3a0da11    mov sp, #69632  ; 0x11000
   10004:   eb000000    bl  1000c <main>
   10008:   eafffffe    b   10008 <_start+0x8>

0001000c <main>:
   1000c:   e3a00005    mov r0, #5
   10010:   e12fff1e    bx  lr


two.elf:     file format elf32-littlearm


Disassembly of section .text:

00010000 <main>:
   10000:   e3a00005    mov r0, #5
   10004:   e12fff1e    bx  lr

00010008 <_start>:
   10008:   e3a0da11    mov sp, #69632  ; 0x11000
   1000c:   ebfffffb    bl  10000 <main>
   10010:   eafffffe    b   10010 <_start+0x8>

如果您将其作为 .bin 文件运行,则需要将 C 引导代码位于地址 0x10000。如果您没有指定部分或对象名称,或者以某种方式告诉链接器专门将某些内容放在那里,那么该工具将按照您在命令行上提供的内容进行处理,并按顺序处理这些内容。因此,如果引导代码首先在命令行上,则该入口点将起作用,但如果您将其他内容放在首位,那么这根本不会起作用,理想情况下会以某种方式崩溃。

现在 qemu 允许 elf 文件,它可能支持也可能不支持 elf 文件中的入口点,如果您在链接描述文件中指定入口点,这可能会起作用,但当然当您随后获取原始二进制映像版本时(-O binary..... .bin) 版本,它将在硬件上失败。除非代码是由精灵加载器或类似的东西(操作系统,像这样支持所有 cr@p 的 sim 环境)加载的,否则只需正确构建文件。(现在了解 cortex-m sims qemu 确实/确实查看了条目的 lsbit 以正确启动 cortex-m,所以你需要它)。

arm-none-eabi-nm -a one.elf | grep start
00010000 T _start
arm-none-eabi-nm -a two.elf | grep start
00010008 T _start

您应该能够删除上面示例中的 ENTRY 并让 one.bin 正常工作。但是 two.bin 不会。也许使用 ENTRY() two.elf 会起作用,但不是你应该依赖的。

在构建裸机时,您应该始终根据硬件(或 sim)检查代码的入口点,以查看您在尝试执行之前是否正确构建了二进制文件。任何新项目或构建基础设施中的任何更改...检查工具链输出。

请注意,如果您正在控制链接描述文件,那么您不需要 _start,即使您不是 ( something-ld -Ttext=0x1000 -Tdata=0x2000) 您也不需要它,它可能会发出警告(对于后者),但谁在乎。_start 被定义为股票链接器脚本中的入口点,一旦您自己制作而不使用股票链接器脚本,您可以根据需要选择入口点的名称和其他名称。

我觉得这很浪费,因为仅仅让命令行正确是微不足道的,但你会看到人们这样做:

闪存.s

.section .init

    ldr sp,=0x11000
    bl main
    b .

.section .text

hello:
    b hello

闪存.ld

MEMORY
{
    ram : ORIGIN = 0x10000, LENGTH = 0x1000
}
SECTIONS
{
    .init   : { *(.init*)   } > ram
    .text   : { *(.text*)   } > ram
    .rodata : { *(.rodata*) } > ram
    .bss    : { *(.bss*)    } > ram
    .data   : { *(.data*)   } > ram
}

构建是相同的:

one.elf:     file format elf32-littlearm


Disassembly of section .init:

00010000 <.init>:
   10000:   e3a0da11    mov sp, #69632  ; 0x11000
   10004:   eb000001    bl  10010 <main>
   10008:   eafffffe    b   10008 <hello-0x4>

Disassembly of section .text:

0001000c <hello>:
   1000c:   eafffffe    b   1000c <hello>

00010010 <main>:
   10010:   e3a00005    mov r0, #5
   10014:   e12fff1e    bx  lr

two.elf:     file format elf32-littlearm


Disassembly of section .init:

00010000 <.init>:
   10000:   e3a0da11    mov sp, #69632  ; 0x11000
   10004:   eb000000    bl  1000c <main>
   10008:   eafffffe    b   10008 <main-0x4>

Disassembly of section .text:

0001000c <main>:
   1000c:   e3a00005    mov r0, #5
   10010:   e12fff1e    bx  lr

00010014 <hello>:
   10014:   eafffffe    b   10014 <hello>

您可以看到基于命令行 (.text) 的 hello 和 main 交换,但在 .text 之前的链接器脚本中专门调用了 .init。

我觉得这是一个丑陋的黑客,YMMV。一个更丑陋的黑客是这样的:

闪存.s

ldr sp,=0x11000
bl main
b .

闪存.ld

MEMORY
{
    ram : ORIGIN = 0x10000, LENGTH = 0x1000
}
SECTIONS
{
    .hello  : { flash.o (.text*)  } > ram
    .text   : { *(.text*)   } > ram
    .rodata : { *(.rodata*) } > ram
    .bss    : { *(.bss*)    } > ram
    .data   : { *(.data*)   } > ram
}

给出:

one.elf:     file format elf32-littlearm


Disassembly of section .hello:

00010000 <.hello>:
   10000:   e3a0da11    mov sp, #69632  ; 0x11000
   10004:   eb000000    bl  1000c <main>
   10008:   eafffffe    b   10008 <main-0x4>

Disassembly of section .text:

0001000c <main>:
   1000c:   e3a00005    mov r0, #5
   10010:   e12fff1e    bx  lr

two.elf:     file format elf32-littlearm


Disassembly of section .hello:

00010000 <.hello>:
   10000:   e3a0da11    mov sp, #69632  ; 0x11000
   10004:   eb000000    bl  1000c <main>
   10008:   eafffffe    b   10008 <main-0x4>

Disassembly of section .text:

0001000c <main>:
   1000c:   e3a00005    mov r0, #5
   10010:   e12fff1e    bx  lr

正如从一开始就提到的:如果您在链接描述文件中明确调用某些内容,它会更改内容,否则它会使用命令行(现在我已经看到了例外情况)。在一天结束时,总是在创建新项目或更改构建时检查反汇编,以查看它正在生成可以运行的二进制文件。(如果地址固定,则入口点在正确的位置,手动组装零件的互通是正确的,等等)。

注意:

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

左侧的 .text 名称是您想要的任何名称,大多数人保留该名称,因为它以传统方式表示某些东西,但您可以在左侧命名这些您想要的名称。编译器使用 .text、.bss、.data 或其他格式,因此您必须正确设置右侧。

MEMORY
{
    ram : ORIGIN = 0x10000, LENGTH = 0x1000
}
SECTIONS
{
    .hello  : { flash.o (.text*)  } > ram
    .world  : { *(.text*)   } > ram
}

Disassembly of section .hello:

00010000 <.hello>:
   10000:   e3a0da11    mov sp, #69632  ; 0x11000
   10004:   eb000000    bl  1000c <main>
   10008:   eafffffe    b   10008 <main-0x4>

Disassembly of section .world:

0001000c <main>:
   1000c:   e3a00005    mov r0, #5
   10010:   e12fff1e    bx  lr

nm 和 readelf 和其他人对此很好。诸如操作系统之类的加载器工具或带有 elf 文件的 qemu 可能希望也可能不希望查看 .bss、.data 等...必须根据具体情况处理。大多数人只是使用传统的名称。

请注意,内存部分上的 ram 名称是您想要的任何名称,您也可以将其称为香蕉,而不是 ram 或 rom 或 flash 或……您看到其他人使用的名称。

于 2021-10-21T03:44:01.403 回答