Skip to content

Commit 93a6614

Browse files
committed
misc: Implement two-pass linking to separate rodata sections.
This patch changes the build to perform two-pass linking to separate rodata with relocations (copied to RAM) from rodata with no relocations (pure data) that can be kept in flash by llext. For dynamic linking mode: - First pass: Link with build-dynamic.ld to create temp ELF (unchanged). - Analyze the elf and extract, sort and write out the sections: - .rodata: sections WITH relocations → copied to RAM (LLEXT_MEM_RODATA) - .rodata_noreloc: sections WITHOUT relocations → kept in flash (LLEXT_MEM_RODATA_NO_RELOC) - Second pass: Link with generated rodata_split.ld + build-dynamic.ld. For static linking mode: - Generates empty rodata_split.ld (no section separation needed) Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
1 parent 5f0e907 commit 93a6614

File tree

3 files changed

+151
-12
lines changed

3 files changed

+151
-12
lines changed

extra/gen_rodata_ld.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package main
2+
3+
import (
4+
"debug/elf"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"sort"
9+
"strings"
10+
)
11+
12+
func main() {
13+
flag.Usage = func() {
14+
fmt.Fprintf(os.Stderr, "Usage: %s <input.elf> [output_script.ld] [link_mode]\n\n", os.Args[0])
15+
fmt.Fprintf(os.Stderr, "Analyzes an ELF file and generates a linker script fragment that\n")
16+
fmt.Fprintf(os.Stderr, "separates .rodata sections into:\n")
17+
fmt.Fprintf(os.Stderr, " .rodata - sections WITH relocations (copied to RAM)\n")
18+
fmt.Fprintf(os.Stderr, " .rodata.noreloc - sections WITHOUT relocations (kept in flash)\n")
19+
fmt.Fprintf(os.Stderr, "\nIf link_mode is 'static', generates an empty linker script.\n")
20+
}
21+
22+
flag.Parse()
23+
if flag.NArg() < 1 {
24+
flag.Usage()
25+
os.Exit(1)
26+
}
27+
28+
inputFile := flag.Arg(0)
29+
outputFile := "rodata_split.ld"
30+
if flag.NArg() >= 2 {
31+
outputFile = flag.Arg(1)
32+
}
33+
linkMode := "dynamic"
34+
if flag.NArg() >= 3 {
35+
linkMode = flag.Arg(2)
36+
}
37+
38+
fmt.Printf("Generate rodata linker script (mode: %s)\n", linkMode)
39+
40+
// For static linking, generate empty linker script
41+
if linkMode == "static" {
42+
out, err := os.Create(outputFile)
43+
if err != nil {
44+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
45+
os.Exit(1)
46+
}
47+
defer out.Close()
48+
49+
fmt.Fprintf(out, "/* Empty linker script for static linking mode */\n")
50+
fmt.Printf("Generated: %s\n", outputFile)
51+
return
52+
}
53+
54+
f, err := elf.Open(inputFile)
55+
if err != nil {
56+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
57+
os.Exit(1)
58+
}
59+
defer f.Close()
60+
61+
// First pass: find all .rodata sections, indexed by section number
62+
type RodataSection struct {
63+
Name string
64+
HasRelocs bool
65+
}
66+
67+
rodataSections := make(map[uint32]*RodataSection)
68+
69+
for i, section := range f.Sections {
70+
if strings.HasPrefix(section.Name, ".rodata") {
71+
rodataSections[uint32(i)] = &RodataSection{Name: section.Name}
72+
}
73+
}
74+
75+
// Second pass: mark which rodata sections have relocations
76+
for _, section := range f.Sections {
77+
if section.Type == elf.SHT_REL || section.Type == elf.SHT_RELA {
78+
if rodata, exists := rodataSections[section.Info]; exists {
79+
rodata.HasRelocs = true
80+
}
81+
}
82+
}
83+
84+
// Separate and sort
85+
withRelocs := []string{}
86+
withoutRelocs := []string{}
87+
88+
for _, rodata := range rodataSections {
89+
if rodata.HasRelocs {
90+
withRelocs = append(withRelocs, rodata.Name)
91+
} else {
92+
withoutRelocs = append(withoutRelocs, rodata.Name)
93+
}
94+
}
95+
96+
sort.Strings(withRelocs)
97+
sort.Strings(withoutRelocs)
98+
99+
// Display
100+
fmt.Printf("rodata with relocations\n")
101+
for _, name := range withRelocs {
102+
fmt.Printf(" -%s\n", name)
103+
}
104+
fmt.Printf("\nrodata without relocations:\n")
105+
for _, name := range withoutRelocs {
106+
fmt.Printf(" -%s\n", name)
107+
}
108+
109+
// Generate linker script
110+
out, err := os.Create(outputFile)
111+
if err != nil {
112+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
113+
os.Exit(1)
114+
}
115+
defer out.Close()
116+
117+
// Helper function to print a section
118+
printSection := func(out *os.File, sectionName string, sections []string) {
119+
fmt.Fprintf(out, " %s : {\n", sectionName)
120+
for _, name := range sections {
121+
fmt.Fprintf(out, " *(%s)\n", name)
122+
}
123+
fmt.Fprintf(out, " }\n\n")
124+
}
125+
126+
fmt.Fprintf(out, "/* Auto-generated linker script fragment for LLEXT\n")
127+
fmt.Fprintf(out, " * Separates .rodata sections based on relocation status\n")
128+
fmt.Fprintf(out, " */\n\n")
129+
fmt.Fprintf(out, "SECTIONS\n{\n")
130+
131+
fmt.Fprintf(out, " /* Read-only data WITH relocations - will be copied to RAM by LLEXT */\n")
132+
printSection(out, ".rodata", withRelocs)
133+
134+
fmt.Fprintf(out, " /* Read-only data WITHOUT relocations - kept in flash by LLEXT */\n")
135+
printSection(out, ".rodata.noreloc", withoutRelocs)
136+
137+
fmt.Fprintf(out, " /* Merge all .rel.rodata.* sections into .rel.rodata */\n")
138+
printSection(out, ".rel.rodata", []string{".rel.rodata", ".rel.rodata.*"})
139+
140+
fmt.Fprintf(out, " /* Merge all .rela.rodata.* sections into .rela.rodata */\n")
141+
printSection(out, ".rela.rodata", []string{".rela.rodata", ".rela.rodata.*"})
142+
143+
fmt.Fprintf(out, "}\n")
144+
145+
fmt.Printf("Generated: %s\n", outputFile)
146+
}

platform.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ build.link_command="{compiler.path}{compiler.c.elf.cmd}" "-L{build.path}" "-L{bu
5858
build.check_command-dynamic={build.link_command} {build.link_args.check-dynamic} -o "{build.path}/{build.project_name}_check.tmp"
5959
build.check_command-static=/bin/true
6060
build.check_command-static.windows=cmd /C cd .
61-
build.combine_command={build.link_command} {build.link_args.build-{build.link_mode}} {build.link_args.build-common}
61+
build.combine_command={build.link_command} {build.link_args.build-{build.link_mode}} "-Wl,-Map,{build.path}/{build.project_name}_temp.map" -o "{build.path}/{build.project_name}_temp.elf"
62+
build.combine_command_final={build.link_command} "-T{build.path}/rodata_split.ld" {build.link_args.build-{build.link_mode}} {build.link_args.build-common}
6263

6364
# link_args.* are included by any link_command depending on the link_mode
6465
build.link_args.dynamic=-e main
@@ -116,9 +117,11 @@ recipe.S.o.pattern="{compiler.path}{compiler.S.cmd}" {compiler.S.flags} -DARDUIN
116117
## Create archives
117118
recipe.ar.pattern="{compiler.path}{compiler.ar.cmd}" {compiler.ar.flags} {compiler.ar.extra_flags} "{archive_file_path}" "{object_file}"
118119

119-
## Combine gc-sections, archives, and objects
120+
## Combine gc-sections, archives, and objects - Two-pass linking
120121
recipe.c.combine.1.pattern={build.check_command-{build.link_mode}}
121122
recipe.c.combine.2.pattern={build.combine_command}
123+
recipe.c.combine.3.pattern="{runtime.platform.path}/extra/gen_rodata_ld" "{build.path}/{build.project_name}_temp.elf" "{build.path}/rodata_split.ld" "{build.link_mode}"
124+
recipe.c.combine.4.pattern={build.combine_command_final}
122125
recipe.c.combine.pattern={build.combine_command}
123126
recipe.hooks.linking.postlink.1.pattern="{compiler.path}{build.crossprefix}strip" --strip-debug "{build.path}/{build.project_name}_debug.elf" -o "{build.path}/{build.project_name}.elf"
124127

variants/_ldscripts/build-dynamic.ld

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@ SECTIONS {
4242
__static_thread_data_list_end = .;
4343
}
4444

45-
.rodata : {
46-
*(.rodata)
47-
*(.rodata1)
48-
*(.rodata.*)
49-
}
50-
5145
.data : {
5246
*(.data .data.*)
5347
}
@@ -82,10 +76,6 @@ SECTIONS {
8276
KEEP(*(.shstrtab))
8377
}
8478

85-
.rel : {
86-
KEEP(*(.rel .rel.*))
87-
}
88-
8979
.got : {
9080
KEEP(*(.got .got.* .got.plt .got.plt*))
9181
}

0 commit comments

Comments
 (0)