为什么使用 arm-none-eabi-ld 链接器更改目标文件的顺序会改变可执行行为?
Why does changing the order of object files using the arm-none-eabi-ld linker change the executable behavior?
我在使用
时出现了不同的行为
arm-none-eabi-ld -T t.ld -o t.elf t.o ts.o
到link我的目标文件,vs
arm-none-eabi-ld -T t.ld -o t.elf ts.o t.o
目标文件 't.o' 和 'ts.o' 在命令中被调换。后一个版本会产生正确的行为,而前一个版本则不会。不同之处似乎是我的程序中的堆栈指针与第一个版本设置不正确,我想知道为什么会这样。
这是我正在使用的源文件和 linker 脚本,以及要编译的脚本。
t.ld
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 =.;
}
t.c
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(使用 linker 命令产生预期结果)
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 使用带有转置目标文件的 linker 命令的结果
(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)明显超出了程序的内存范围。
更简单:
flash.s
.global _start
_start:
ldr sp,=0x11000
bl main
b .
flash.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 bootstrap 代码位于地址 0x10000。如果您没有指定部分或对象名称,或者以某种方式告诉链接器专门将某些内容放在那里,那么该工具将按照您在命令行上提供的内容进行处理,并按顺序处理这些内容。因此,如果 bootstrap 代码首先出现在命令行上,那么该入口点将起作用,但如果您先放置其他内容,那么它根本不会起作用,并且理想情况下会以某种方式崩溃。
现在 qemu 允许使用 elf 文件,它可能支持也可能不支持 elf 文件中的入口点,如果您在链接描述文件中指定入口点,这可能会起作用,但当然当您随后采用原始二进制映像版本 (-O binary....... .bin) 版本将在硬件上失败。除非代码是由 elf 加载器或类似的东西(操作系统和像这样支持所有 cr@p 的 sim 环境)加载的,否则只需正确构建文件即可。 (现在了解 cortex-m sims qemu does/did 查看条目的 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 被定义为库存链接器脚本中的入口点,一旦你自己制作而不使用库存的,你可以根据需要选择入口点的名称和其他东西。
我发现它很浪费,因为正确设置命令行是微不足道的,但你会看到人们这样做:
flash.s
.section .init
ldr sp,=0x11000
bl main
b .
.section .text
hello:
b hello
flash.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>
您可以看到 hello 和 main 交换基于命令行 (.text),但是 .init 在 .text 之前在链接器脚本中被专门调用。
我发现这是一个丑陋的 hack,YMMV。一个更丑陋的 hack 是这样的:
flash.s
ldr sp,=0x11000
bl main
b .
flash.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 名称是您想要的任何名称,您也可以将其称为 banana 而不是 ram、rom 或 flash 或...您看到其他人使用的名称。
我在使用
时出现了不同的行为arm-none-eabi-ld -T t.ld -o t.elf t.o ts.o
到link我的目标文件,vs
arm-none-eabi-ld -T t.ld -o t.elf ts.o t.o
目标文件 't.o' 和 'ts.o' 在命令中被调换。后一个版本会产生正确的行为,而前一个版本则不会。不同之处似乎是我的程序中的堆栈指针与第一个版本设置不正确,我想知道为什么会这样。
这是我正在使用的源文件和 linker 脚本,以及要编译的脚本。
t.ld
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 =.;
}
t.c
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(使用 linker 命令产生预期结果)
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 使用带有转置目标文件的 linker 命令的结果
(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)明显超出了程序的内存范围。
更简单:
flash.s
.global _start
_start:
ldr sp,=0x11000
bl main
b .
flash.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 bootstrap 代码位于地址 0x10000。如果您没有指定部分或对象名称,或者以某种方式告诉链接器专门将某些内容放在那里,那么该工具将按照您在命令行上提供的内容进行处理,并按顺序处理这些内容。因此,如果 bootstrap 代码首先出现在命令行上,那么该入口点将起作用,但如果您先放置其他内容,那么它根本不会起作用,并且理想情况下会以某种方式崩溃。
现在 qemu 允许使用 elf 文件,它可能支持也可能不支持 elf 文件中的入口点,如果您在链接描述文件中指定入口点,这可能会起作用,但当然当您随后采用原始二进制映像版本 (-O binary....... .bin) 版本将在硬件上失败。除非代码是由 elf 加载器或类似的东西(操作系统和像这样支持所有 cr@p 的 sim 环境)加载的,否则只需正确构建文件即可。 (现在了解 cortex-m sims qemu does/did 查看条目的 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 被定义为库存链接器脚本中的入口点,一旦你自己制作而不使用库存的,你可以根据需要选择入口点的名称和其他东西。
我发现它很浪费,因为正确设置命令行是微不足道的,但你会看到人们这样做:
flash.s
.section .init
ldr sp,=0x11000
bl main
b .
.section .text
hello:
b hello
flash.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>
您可以看到 hello 和 main 交换基于命令行 (.text),但是 .init 在 .text 之前在链接器脚本中被专门调用。
我发现这是一个丑陋的 hack,YMMV。一个更丑陋的 hack 是这样的:
flash.s
ldr sp,=0x11000
bl main
b .
flash.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 名称是您想要的任何名称,您也可以将其称为 banana 而不是 ram、rom 或 flash 或...您看到其他人使用的名称。