您的问题让人感到困惑。首先,为什么任何链接器都要把精力放在随机化上?也许有一个链接器是故意做出输出不可重复的结果。但是,链接器只是一个程序,通常会按顺序处理命令行中的项目,然后从开始到结束处理每个对象...而不是随机处理。
到目前为止,其他部分似乎都很简单,只需使用工具即可。您的评论暗示了GNU工具?由于这在某种程度上是特定于工具的,因此您应该标记它,因为您不能真正在所有曾经创建的工具链之间进行概括。
unsigned int one ( void )
{
return(1)
}
unsigned int two ( void )
{
return(2)
}
unsigned int three ( void )
{
return(3)
}
arm-none-eabi-gcc -O2 -c so.c -o so.o
arm-none-eabi-objdump -d so.o
so.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <one>:
0: e3a00001 mov r0, #1
4: e12fff1e bx lr
00000008 <two>:
8: e3a00002 mov r0, #2
c: e12fff1e bx lr
00000010 <three>:
10: e3a00003 mov r0, #3
14: e12fff1e bx lr
如图所示,它们都在.text中,非常简单。
arm-none-eabi-gcc -O2 -c -ffunction-sections so.c -o so.o
arm-none-eabi-objdump -d so.o
so.o: file format elf32-littlearm
Disassembly of section .text.one:
00000000 <one>:
0: e3a00001 mov r0, #1
4: e12fff1e bx lr
Disassembly of section .text.two:
00000000 <two>:
0: e3a00002 mov r0, #2
4: e12fff1e bx lr
Disassembly of section .text.three:
00000000 <three>:
0: e3a00003 mov r0, #3
4: e12fff1e bx lr
现在每个函数都有自己的章节名称。
因此,余下的工作大量依赖链接,没有一个通用的链接脚本,由程序员直接或间接进行选择,最终二进制文件(elf)的构建结果也是这个选择的直接结果。
如果您有类似以下代码:
.text : { *(.text*) } > rom
如果这些函数在定义时没有其他约束,则所有这些函数都将归入此定义,但链接器脚本或链接器指令可以指示其他情况,使一个或多个函数归入自身的空间。
arm-none-eabi-ld -Ttext=0x1000 so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start
arm-none-eabi-objdump -d so.elf
so.elf: file format elf32-littlearm
Disassembly of section .text:
00001000 <one>:
1000: e3a00001 mov r0, #1
1004: e12fff1e bx lr
00001008 <two>:
1008: e3a00002 mov r0, #2
100c: e12fff1e bx lr
00001010 <three>:
1010: e3a00003 mov r0, #3
1014: e12fff1e bx lr
当然,接下来
arm-none-eabi-nm -a so.elf
00000000 n .ARM.attributes
00011018 T __bss_end__
00011018 T _bss_end__
00011018 T __bss_start
00011018 T __bss_start__
00000000 n .comment
00011018 T __data_start
00011018 T _edata
00011018 T _end
00011018 T __end__
00011018 ? .noinit
00001000 T one <----
00000000 a so.c
00080000 T _stack
U _start
00001000 t .text
00001010 T three <----
00001008 T two <----
这是因为文件中有符号表。
Symbol table '.symtab' contains 22 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00001000 0 SECTION LOCAL DEFAULT 1
2: 00000000 0 SECTION LOCAL DEFAULT 2
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00011018 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 FILE LOCAL DEFAULT ABS so.c
6: 00001000 0 NOTYPE LOCAL DEFAULT 1 $a
7: 00001008 0 NOTYPE LOCAL DEFAULT 1 $a
8: 00001010 0 NOTYPE LOCAL DEFAULT 1 $a
9: 00001008 8 FUNC GLOBAL DEFAULT 1 two
10: 00011018 0 NOTYPE GLOBAL DEFAULT 1 _bss_end__
11: 00011018 0 NOTYPE GLOBAL DEFAULT 1 __bss_start__
12: 00011018 0 NOTYPE GLOBAL DEFAULT 1 __bss_end__
13: 00000000 0 NOTYPE GLOBAL DEFAULT UND _start
14: 00011018 0 NOTYPE GLOBAL DEFAULT 1 __bss_start
15: 00011018 0 NOTYPE GLOBAL DEFAULT 1 __end__
16: 00001000 8 FUNC GLOBAL DEFAULT 1 one
17: 00011018 0 NOTYPE GLOBAL DEFAULT 1 _edata
18: 00011018 0 NOTYPE GLOBAL DEFAULT 1 _end
19: 00080000 0 NOTYPE GLOBAL DEFAULT 1 _stack
20: 00001010 8 FUNC GLOBAL DEFAULT 1 three
21: 00011018 0 NOTYPE GLOBAL DEFAULT 1 __data_start
但是如果。
arm-none-eabi-strip so.elf
arm-none-eabi-nm -a so.elf
arm-none-eabi-nm: so.elf: no symbols
arm-none-eabi-objdump -d so.elf
so.elf: file format elf32-littlearm
Disassembly of section .text:
00001000 <.text>:
1000: e3a00001 mov r0, #1
1004: e12fff1e bx lr
1008: e3a00002 mov r0, #2
100c: e12fff1e bx lr
1010: e3a00003 mov r0, #3
1014: e12fff1e bx lr
ELF文件格式相当简单,您可以轻松编写代码来解析它,不需要使用库或其他任何东西。通过像这样的简单实验,您可以轻松理解这些工具的工作方式。
如何获得此函数使用的初始内存?
假设您指的是未重定位的初始地址。您只需从文件中读取即可。简单吧。
每个函数都有一个内存段以供访问,还是随机的?
如上所示,您在评论中稍后提到的命令行选项(应该在问题中进行编辑以完整),正是每个函数创建自定义节名称的方式。(如果您在两个或多个对象中具有相同的非全局函数名称,会发生什么情况?您可以轻松地自行弄清楚)
这里没有任何随机性,您需要有一个安全或其他原因来随机化事物,更经常地,希望工具每次使用相同的输入输出相同或至少类似的结果(某些工具将构建日期/时间嵌入文件中,这可能会因每次构建而异)。
如果您没有使用gnu工具,则binutils仍然非常有用,可以对解析和显示ELF文件进行处理。
arm-none-eabi-nm so.elf
00011018 T __bss_end__
00011018 T _bss_end__
00011018 T __bss_start
00011018 T __bss_start__
00011018 T __data_start
00011018 T _edata
00011018 T _end
00011018 T __end__
00001000 T one
00080000 T _stack
U _start
00001010 T three
00001008 T two
nm so.elf (x86 binutils not arm)
00001000 t $a
00001008 t $a
00001010 t $a
00011018 T __bss_end__
00011018 T _bss_end__
00011018 T __bss_start
00011018 T __bss_start__
00011018 T __data_start
00011018 T _edata
00011018 T _end
00011018 T __end__
00001000 T one
00080000 T _stack
U _start
00001010 T three
00001008 T two
可以用clang进行构建,然后使用gnu等工具进行分析。显然,反汇编不起作用,但是一些工具可以使用。
通常情况下是不行的。术语“函数”意味着但不限于高级语言如C等,在这种情况下,机器代码显然不知道也不应该知道所使用的内存地址范围,而且经过优化的代码不一定有一个单独的从函数退出的点,更不用说标记结束的返回了。对于像各种ARM指令集这样的体系结构,返回指令并不代表“函数”的结束,可能还会跟随一些池数据。
但是让我们来看看gcc的处理方式。
unsigned int one ( unsigned int x )
{
return(x+1)
}
unsigned int two ( void )
{
return(one(2))
}
unsigned int three ( void )
{
return(3)
}
arm-none-eabi-gcc -O2 -S so.c
cat so.s
.cpu arm7tdmi
.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 "so.c"
.text
.align 2
.global one
.arch armv4t
.syntax unified
.arm
.fpu softvfp
.type one, %function
one:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
add r0, r0, #1
bx lr
.size one, .-one
.align 2
.global two
.syntax unified
.arm
.fpu softvfp
.type two, %function
two:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
mov r0, #3
bx lr
.size two, .-two
.align 2
.global three
.syntax unified
.arm
.fpu softvfp
.type three, %function
three:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
mov r0, #3
bx lr
.size three, .-three
.ident "GCC: (GNU) 10.2.0"
我们看到这被放置在文件中,但它是做什么用的?
.size three, .-three
有一种说法是这样使用的,以便链接器可以在函数未被使用时将其删除。我见过这个特性正在发挥作用,所以知道这一点很好(你和我一样可以轻松查找)。
因此,在那种情况下,信息是存在的,你可以提取它(读者应该记住这一点)。
然后,如果你使用了你提到的gcc编译器选项-ffunction-sections
Disassembly of section .text.one:
00000000 <one>:
0: e2800001 add r0, r0, #1
4: e12fff1e bx lr
Disassembly of section .text.two:
00000000 <two>:
0: e3a00003 mov r0, #3
4: e12fff1e bx lr
Disassembly of section .text.three:
00000000 <three>:
0: e3a00003 mov r0, #3
4: e12fff1e bx lr
[ 4] .text.one
PROGBITS 00000000 000034 000008 00 0 0 4
[00000006]: ALLOC, EXEC
[ 5] .rel.text.one
REL 00000000 0001a4 000008 08 12 4 4
[00000040]: INFO LINK
[ 6] .text.two
PROGBITS 00000000 00003c 000008 00 0 0 4
[00000006]: ALLOC, EXEC
[ 7] .rel.text.two
REL 00000000 0001ac 000008 08 12 6 4
[00000040]: INFO LINK
[ 8] .text.three
PROGBITS 00000000 000044 000008 00 0 0 4
[00000006]: ALLOC, EXEC
[ 9] .rel.text.three
REL 00000000 0001b4 000008 08 12 8 4
[00000040]: INFO LINK
这给我们提供了每个部分的大小。
一般来说,就编译软件而言,或者特别是装配软件,假设一个函数没有边界。如上所述,一个函数被内联到另一个函数中,在另一个函数中内联的函数有多大?在二进制文件中有多少个函数实例?你想监视哪一个函数并知道其大小,性能等?Gnu在gcc中具有这个功能,您可以使用其他语言或工具查看是否存在。假设答案是否定的,然后如果您碰巧找到了一种方法,那就很好。
编译器是否将某个内存段保存为只能由某个特定函数访问?
我不知道这是什么意思。编译器并不会创建内存段,链接器会。如何将二进制文件放入内存映像是链接器的事情,而不是编译器的事情。段只是一种沟通方式,在工具之间传递信息,用于指明这些字节最初是代码(理想情况下为只读),已初始化数据或未初始化数据。也许可以扩展到只读数据,然后自己构建类型。
如果您的终极目标是通过使用gnu工具链查看elf二进制文件来找到表示内存中“函数”高级概念的字节(假设没有重定位等),那么这在理论上是可能的。
我们似乎首先要知道的是,对象包含此信息,以便链接器功能可以删除未使用的函数以减小大小。但是,这并不自动意味着链接器的输出二进制文件也包括此信息。您需要找到此 .size 在对象中的位置,然后在最终的二进制文件中查找。
编译器将一种语言转换为另一种语言,通常从更高级别到更低级别,但并非始终如此,这取决于编译器和输入/输出语言。例如,C到汇编语言或C到机器码,或者Verilog到C ++用于模拟,这是否更高级或更低级?.text、.data、.bss等术语不是语言的一部分,而是基于学习经验的一种习惯,并有助于与链接器沟通,以便针对各种目标控制输出二进制文件。通常情况下,如上所示,编译器(在此情况下是gcc)由于无法在所有工具和语言或甚至所有C或C ++工具中制定普遍规则,因此源文件中所有函数的所有代码都会默认落在一个 .text 段中。如果要得到不同的东西,需要额外的工作。所以通常情况下,编译器不为每个 ...一般情况下制作“段”或“内存段”。您似乎已经通过使用命令行选项将每个函数都变成自己的段来解决了问题,现在您对大小、位置等有更多的控制。
只需使用文件格式和/或工具即可。这个问题或者一系列问题可以简化为查看elf文件格式。这不是堆栈溢出问题,因为寻求外部信息的问题不适用于此网站。
编译器是否将内存位置地址从0x20到0x1234保存,以便仅在执行该基本块期间访问?换句话说,函数与其使用的内存地址范围之间是否存在映射关系?
“保存”?编译器不连接,链接器连接。该内存仅在执行该块时访问吗?在纯理论教科书中是这样的,但在现实中,分支预测、预取或缓存行填充也可以访问该“内存”。
除非使用自修改代码或以有趣的方式使用mmu,否则您不应在应用程序中为多个函数重复使用地址空间。通常情况下,foo()函数和bar()函数都在不同的地方实现。在过去的手写汇编语言中,您可能会使foo()直接跳转到bar()的中间部分以节省空间、获得更好的性能或使代码难以逆向工程等。但编译器并不那么高效,它们尽力将类似函数的概念转化为首先是功能(与高级代码相等),然后是相对于直接在语言之间进行粗暴转换而言更小或更快或两者兼而有之的东西。因此,除了内联和尾部(叶子?我称其为尾部)优化等之外,可以说在某个地址处有一些字节定义了已编译的函数。但由于处理器技术的性质,您不能假设这些字节仅在处理器/芯片/系统总线在执行该功能时访问。