Skip to content

Commit d6bef65

Browse files
committed
extra/gen_rodata_ld: Add gen_rodata_ld tool.
Analyzes an elf and extracts, sorts and writes out its rodata sections: - .rodata: sections WITH relocations → copied to RAM (LLEXT_MEM_RODATA) - .rodata_noreloc: sections WITHOUT relocations → kept in flash (LLEXT_MEM_RODATA_NO_RELOC) Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
1 parent 5f0e907 commit d6bef65

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

extra/gen-rodata-ld/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## Rodata Linker Script Generator
2+
3+
This tool analyzes ELF files and generates linker script fragments for Zephyr
4+
two-pass linking, separating read-only data sections based on relocations.
5+
6+
The tool examines `.rodata` sections in a temporary ELF file and generates a linker
7+
script that places sections with relocations into `.rodata` (copied to RAM by LLEXT)
8+
and sections without relocations into `.rodata.noreloc` (kept in flash). This
9+
optimization significantly reduces RAM usage for LLEXT applications.
10+
11+
### Getting the tool
12+
13+
If you have installed the Arduino IDE and the Arduino core for Zephyr, you can
14+
find the pre-built files in the `.arduino15/packages/arduino/tools/` folder in
15+
your home directory. You can directly use the tool from there.
16+
17+
### Building manually
18+
19+
To build the tool, you need to have the Go programming language installed; make
20+
sure you have the `go` command available in your PATH. Then, use the `go build`
21+
command to build the tool for your platform.
22+
23+
To build the full set of binaries for all platforms, run the `package_tool.sh`
24+
script in the parent directory with `../package_tool.sh`, or provide the path
25+
to this directory as an argument.

extra/gen-rodata-ld/main.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
// Generate linker script
100+
out, err := os.Create(outputFile)
101+
if err != nil {
102+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
103+
os.Exit(1)
104+
}
105+
defer out.Close()
106+
107+
// Helper function to print a section
108+
printSection := func(out *os.File, sectionName string, sections []string) {
109+
fmt.Fprintf(out, " %s : {\n", sectionName)
110+
for _, name := range sections {
111+
fmt.Fprintf(out, " *(%s)\n", name)
112+
}
113+
fmt.Fprintf(out, " }\n\n")
114+
}
115+
116+
fmt.Fprintf(out, "/* Auto-generated linker script fragment for LLEXT\n")
117+
fmt.Fprintf(out, " * Separates .rodata sections based on relocation status\n")
118+
fmt.Fprintf(out, " */\n\n")
119+
fmt.Fprintf(out, "SECTIONS\n{\n")
120+
121+
fmt.Fprintf(out, " /* Read-only data WITH relocations - will be copied to RAM by LLEXT */\n")
122+
printSection(out, ".rodata", withRelocs)
123+
124+
fmt.Fprintf(out, " /* Read-only data WITHOUT relocations - kept in flash by LLEXT */\n")
125+
printSection(out, ".rodata.noreloc", withoutRelocs)
126+
127+
fmt.Fprintf(out, " /* Merge all .rel.rodata.* sections into .rel.rodata */\n")
128+
printSection(out, ".rel.rodata", []string{".rel.rodata", ".rel.rodata.*"})
129+
130+
fmt.Fprintf(out, " /* Merge all .rela.rodata.* sections into .rela.rodata */\n")
131+
printSection(out, ".rela.rodata", []string{".rela.rodata", ".rela.rodata.*"})
132+
133+
fmt.Fprintf(out, "}\n")
134+
135+
fmt.Printf("Generated: %s\n", outputFile)
136+
}

0 commit comments

Comments
 (0)