Skip to content

Commit a413070

Browse files
committed
Add Lidia binary layout support
1 parent 5512853 commit a413070

File tree

6 files changed

+208
-8
lines changed

6 files changed

+208
-8
lines changed

lidia/binary_layout.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package lidia
2+
3+
// BinaryLayoutInfo represents the layout information of the binary
4+
type BinaryLayoutInfo struct {
5+
Type uint16 // e_type from ELF header
6+
ProgramHeaders []ProgramHeaderInfo
7+
}
8+
9+
// ProgramHeaderInfo represents a program header from the ELF file
10+
type ProgramHeaderInfo struct {
11+
Type uint32
12+
Flags uint32
13+
Offset uint64
14+
VirtualAddr uint64
15+
PhysAddr uint64
16+
FileSize uint64
17+
MemSize uint64
18+
Align uint64
19+
}

lidia/builder.go

+45-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package lidia
22

33
import (
44
"encoding/binary"
5+
"fmt"
56
"sort"
67
)
78

@@ -104,9 +105,10 @@ func (s *sortByVADepth) Swap(i, j int) {
104105

105106
// rangeCollector
106107
type rangeCollector struct {
107-
sb *stringBuilder
108-
rb *rangesBuilder
109-
lb *lineBuilder
108+
sb *stringBuilder
109+
rb *rangesBuilder
110+
lb *lineBuilder
111+
blb *binaryLayoutBuilder
110112

111113
opt options
112114
}
@@ -135,3 +137,43 @@ func (rc *rangeCollector) VisitRange(r *Range) {
135137
}
136138
rc.rb.add(r.VA, e)
137139
}
140+
141+
type binaryLayoutBuilder struct {
142+
buf []byte
143+
}
144+
145+
func newBinaryLayoutBuilder() *binaryLayoutBuilder {
146+
return &binaryLayoutBuilder{
147+
buf: make([]byte, 0, 256),
148+
}
149+
}
150+
151+
func (blb *binaryLayoutBuilder) write(layout *BinaryLayoutInfo) error {
152+
if layout == nil {
153+
return fmt.Errorf("nil binary layout")
154+
}
155+
156+
// Reset buffer
157+
blb.buf = blb.buf[:0]
158+
159+
// Write ELF type
160+
blb.buf = binary.LittleEndian.AppendUint16(blb.buf, layout.Type)
161+
162+
// Write number of program headers
163+
count := uint32(len(layout.ProgramHeaders))
164+
blb.buf = binary.LittleEndian.AppendUint32(blb.buf, count)
165+
166+
// Write each program header
167+
for _, ph := range layout.ProgramHeaders {
168+
blb.buf = binary.LittleEndian.AppendUint32(blb.buf, ph.Type)
169+
blb.buf = binary.LittleEndian.AppendUint32(blb.buf, ph.Flags)
170+
blb.buf = binary.LittleEndian.AppendUint64(blb.buf, ph.Offset)
171+
blb.buf = binary.LittleEndian.AppendUint64(blb.buf, ph.VirtualAddr)
172+
blb.buf = binary.LittleEndian.AppendUint64(blb.buf, ph.PhysAddr)
173+
blb.buf = binary.LittleEndian.AppendUint64(blb.buf, ph.FileSize)
174+
blb.buf = binary.LittleEndian.AppendUint64(blb.buf, ph.MemSize)
175+
blb.buf = binary.LittleEndian.AppendUint64(blb.buf, ph.Align)
176+
}
177+
178+
return nil
179+
}

lidia/constants.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ const (
1111
version uint32 = 1
1212

1313
// Size of the file header in bytes
14-
headerSize = 0x80
14+
headerSize = 0x98
1515

1616
// Number of fields in a line table entry
1717
lineTableFieldsCount = 2
1818

19+
// Size of each line table entry (2 uint16s or 2 uint32s)
20+
lineTableFieldsSize = 4
21+
1922
// Number of fields in a range entry
2023
fieldsCount = 8
2124

lidia/format.go

+34
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ type lineTablesHeader struct {
5454
_ uint32
5555
}
5656

57+
type binaryLayoutHeader struct {
58+
size uint64
59+
offset uint64
60+
crc uint32
61+
_ uint32
62+
}
63+
5764
type header struct {
5865
// 0x0
5966
magic [4]byte
@@ -67,6 +74,8 @@ type header struct {
6774
// 0x60
6875
lineTablesHeader lineTablesHeader
6976
// 0x80
77+
binaryLayoutHeader binaryLayoutHeader
78+
// 0x98
7079
}
7180

7281
func readHeader(file io.Reader) (header, error) {
@@ -97,6 +106,10 @@ func readHeader(file io.Reader) (header, error) {
97106
hdr.lineTablesHeader.offset = binary.LittleEndian.Uint64(headerBuf[0x70:])
98107
hdr.lineTablesHeader.crc = binary.LittleEndian.Uint32(headerBuf[0x78:])
99108

109+
hdr.binaryLayoutHeader.size = binary.LittleEndian.Uint64(headerBuf[0x80:])
110+
hdr.binaryLayoutHeader.offset = binary.LittleEndian.Uint64(headerBuf[0x88:])
111+
hdr.binaryLayoutHeader.crc = binary.LittleEndian.Uint32(headerBuf[0x90:])
112+
100113
return hdr, nil
101114
}
102115

@@ -166,6 +179,23 @@ func (rc *rangeCollector) write(f io.WriteSeeker) error {
166179
return err
167180
}
168181

182+
// Flush buffer to ensure line tables are written
183+
if err := buf.Flush(); err != nil {
184+
return err
185+
}
186+
187+
hdr.binaryLayoutHeader.offset = hdr.lineTablesHeader.offset + uint64(len(rc.lb.entries)*lineTableFieldsSize)
188+
hdr.binaryLayoutHeader.size = uint64(len(rc.blb.buf))
189+
190+
if len(rc.blb.buf) > 0 {
191+
if _, err := f.Write(rc.blb.buf); err != nil {
192+
return err
193+
}
194+
crc := crc32.New(castagnoli)
195+
_, _ = crc.Write(rc.blb.buf)
196+
hdr.binaryLayoutHeader.crc = crc.Sum32()
197+
}
198+
169199
if _, err := f.Seek(0, io.SeekStart); err != nil {
170200
return err
171201
}
@@ -200,6 +230,10 @@ func writeHeader(output io.Writer, hdr *header) error {
200230
binary.LittleEndian.PutUint64(headerBuf[0x70:], hdr.lineTablesHeader.offset)
201231
binary.LittleEndian.PutUint32(headerBuf[0x78:], hdr.lineTablesHeader.crc)
202232

233+
binary.LittleEndian.PutUint64(headerBuf[0x80:], hdr.binaryLayoutHeader.size)
234+
binary.LittleEndian.PutUint64(headerBuf[0x88:], hdr.binaryLayoutHeader.offset)
235+
binary.LittleEndian.PutUint32(headerBuf[0x90:], hdr.binaryLayoutHeader.crc)
236+
203237
if _, err := output.Write(headerBuf); err != nil {
204238
return err
205239
}

lidia/lidia.go

+103-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package lidia
77

88
import (
99
"debug/elf"
10+
"encoding/binary"
1011
"fmt"
12+
"hash/crc32"
1113
"io"
1214
"os"
1315
"sort"
@@ -156,7 +158,33 @@ func CreateLidiaFromELF(elfFile *elf.File, output io.WriteSeeker, opts ...Option
156158
sb := newStringBuilder()
157159
rb := newRangesBuilder()
158160
lb := newLineTableBuilder()
159-
rc := &rangeCollector{sb: sb, rb: rb, lb: lb}
161+
blb := newBinaryLayoutBuilder()
162+
rc := &rangeCollector{sb: sb, rb: rb, lb: lb, blb: blb}
163+
164+
// Extract binary layout
165+
layout := &BinaryLayoutInfo{
166+
Type: uint16(elfFile.Type),
167+
ProgramHeaders: make([]ProgramHeaderInfo, 0),
168+
}
169+
170+
for _, prog := range elfFile.Progs {
171+
if prog.Type == elf.PT_LOAD {
172+
layout.ProgramHeaders = append(layout.ProgramHeaders, ProgramHeaderInfo{
173+
Type: uint32(prog.Type),
174+
Flags: uint32(prog.Flags),
175+
Offset: prog.Off,
176+
VirtualAddr: prog.Vaddr,
177+
PhysAddr: prog.Paddr,
178+
FileSize: prog.Filesz,
179+
MemSize: prog.Memsz,
180+
Align: prog.Align,
181+
})
182+
}
183+
}
184+
185+
if err := blb.write(layout); err != nil {
186+
return fmt.Errorf("failed to write binary layout: %w", err)
187+
}
160188

161189
for _, o := range opts {
162190
o(&rc.opt)
@@ -237,6 +265,69 @@ func (st *Table) Lookup(dst []SourceInfoFrame, addr uint64) ([]SourceInfoFrame,
237265
return dst, nil
238266
}
239267

268+
func (st *Table) GetBinaryLayout() (*BinaryLayoutInfo, error) {
269+
if st.hdr.binaryLayoutHeader.size == 0 {
270+
return nil, fmt.Errorf("binary layout information not available")
271+
}
272+
273+
// Read binary layout data
274+
data := make([]byte, st.hdr.binaryLayoutHeader.size)
275+
if _, err := st.file.ReadAt(data, int64(st.hdr.binaryLayoutHeader.offset)); err != nil {
276+
return nil, fmt.Errorf("failed to read binary layout: %w", err)
277+
}
278+
279+
// Verify CRC if enabled
280+
if st.opt.crc {
281+
crc := crc32.Checksum(data, crc32.MakeTable(crc32.Castagnoli))
282+
if crc != st.hdr.binaryLayoutHeader.crc {
283+
return nil, fmt.Errorf("binary layout CRC mismatch")
284+
}
285+
}
286+
287+
// Decode binary layout
288+
if len(data) < 6 {
289+
return nil, fmt.Errorf("binary layout data too short")
290+
}
291+
292+
layout := &BinaryLayoutInfo{
293+
Type: binary.LittleEndian.Uint16(data[:2]),
294+
}
295+
296+
count := binary.LittleEndian.Uint32(data[2:6])
297+
if count > 1000 {
298+
return nil, fmt.Errorf("invalid program header count: %d", count)
299+
}
300+
301+
expectedSize := uint64(6 + count*56)
302+
if uint64(len(data)) < expectedSize {
303+
return nil, fmt.Errorf("binary layout data truncated")
304+
}
305+
306+
layout.ProgramHeaders = make([]ProgramHeaderInfo, count)
307+
offset := 6
308+
for i := range layout.ProgramHeaders {
309+
ph := &layout.ProgramHeaders[i]
310+
ph.Type = binary.LittleEndian.Uint32(data[offset:])
311+
offset += 4
312+
ph.Flags = binary.LittleEndian.Uint32(data[offset:])
313+
offset += 4
314+
ph.Offset = binary.LittleEndian.Uint64(data[offset:])
315+
offset += 8
316+
ph.VirtualAddr = binary.LittleEndian.Uint64(data[offset:])
317+
offset += 8
318+
ph.PhysAddr = binary.LittleEndian.Uint64(data[offset:])
319+
offset += 8
320+
ph.FileSize = binary.LittleEndian.Uint64(data[offset:])
321+
offset += 8
322+
ph.MemSize = binary.LittleEndian.Uint64(data[offset:])
323+
offset += 8
324+
ph.Align = binary.LittleEndian.Uint64(data[offset:])
325+
offset += 8
326+
}
327+
328+
return layout, nil
329+
}
330+
240331
// Close releases resources associated with the Table.
241332
func (st *Table) Close() {
242333
if st.file != nil {
@@ -258,5 +349,16 @@ func (st *Table) CheckCRC() error {
258349
if err := st.CheckCRCLineTables(); err != nil {
259350
return err
260351
}
352+
353+
// Add binary layout CRC check
354+
if st.hdr.binaryLayoutHeader.size > 0 {
355+
if err := checkCRC(st.file,
356+
int64(st.hdr.binaryLayoutHeader.offset),
357+
int64(st.hdr.binaryLayoutHeader.size),
358+
st.hdr.binaryLayoutHeader.crc,
359+
"binary layout"); err != nil {
360+
return err
361+
}
362+
}
261363
return nil
262364
}

lidia/lidia_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import (
88
"path/filepath"
99
"testing"
1010

11-
"github.com/grafana/pyroscope/lidia"
12-
1311
"github.com/stretchr/testify/require"
12+
13+
"github.com/grafana/pyroscope/lidia"
1414
)
1515

1616
const compressedTestBinaryFile = "testdata/test-binary.zip"
17-
const decompressedDir = "decompressed" // Will be created in the temp dir
17+
const decompressedDir = "decompressed"
1818

1919
// TestCreateLidia tests the CreateLidia function with the test binary
2020
func TestCreateLidia(t *testing.T) {

0 commit comments

Comments
 (0)