-
Notifications
You must be signed in to change notification settings - Fork 19
/
source.go
1079 lines (921 loc) · 31.4 KB
/
source.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package dwarf
import (
"debug/dwarf"
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/jetsetilly/gopher2600/coprocessor"
"github.com/jetsetilly/gopher2600/coprocessor/developer/profiling"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
"github.com/jetsetilly/gopher2600/logger"
)
// Sentinal error to indicate that the DWARF data isn't supported by the
// package. It might be valid DWARF but we don't want to deal with it
var UnsupportedDWARF = errors.New("unsupported DWARF")
// Cartridge defines the interface to the cartridge required by the source package
type Cartridge interface {
GetCoProcBus() coprocessor.CartCoProcBus
}
// compile units are made up of many children. for convenience/speed we keep
// track of the children as an index rather than a tree.
type compileUnit struct {
unit *dwarf.Entry
children map[dwarf.Offset]*dwarf.Entry
address uint64
}
// Source is created from available DWARF data that has been found in relation
// to and ELF file that looks to be related to the specified ROM.
//
// It is possible for the arrays/map fields to be empty
type Source struct {
cart Cartridge
// simplified path to use
path string
// ELF sections that help DWARF locate local variables in memory
debugLoc *loclistSection
debugFrame *frameSection
// source is compiled with optimisation
Optimised bool
// every compile unit in the dwarf data
compileUnits []*compileUnit
// instructions in the source code
Instructions map[uint64]*SourceInstruction
// all the files in all the compile units
Files map[string]*SourceFile
Filenames []string
// as above but indexed by the file's short filename, which is sometimes
// more useful than the full name
//
// short filenames also only include files that are in the same path as the
// ROM file
FilesByShortname map[string]*SourceFile
ShortFilenames []string
// functions found in the compile units
Functions map[string]*SourceFunction
FunctionNames []string
// best guess at what the "main" function is in the program. very often
// this function will be called "main" and will be easy to discern but
// sometimes it is named something else and we must figure out as best we
// can which function it is
//
// if no function can be found at all, MainFunction will be a stub entry
MainFunction *SourceFunction
// special purpose line used to collate instructions that are outside the
// loaded ROM and are very likely instructions handled by the "driver". the
// actual driver function is in the Functions map as normal, under the name
// given in "const driverFunction"
DriverSourceLine *SourceLine
// sorted list of every function in all compile unit
SortedFunctions SortedFunctions
// all global variables in all compile units
GlobalsByAddress map[uint64]*SourceVariable
SortedGlobals SortedVariables
// all local variables in all compile units
SortedLocals SortedVariablesLocal
// the highest address of any variable (not just global variables, any
// variable)
HighAddress uint64
// lines of source code found in the compile units. this is a sparse
// coverage of the total address space
LinesByAddress map[uint64]*SourceLine
// sorted list of every source line in all compile units
SortedLines SortedLines
// every non-blank line of source code in all compile units
AllLines AllSourceLines
// sorted lines filtered by function name
FunctionFilters []*FunctionFilter
// profiling for the entire program
Cycles profiling.Cycles
// flag to indicate whether the execution profile has changed since it was cleared
//
// cheap and easy way to prevent sorting too often - rather than sort after
// every call to execute(), we can use this flag to sort only when we need
// to in the GUI.
//
// probably not scalable but sufficient for our needs of a single GUI
// running and using the profiling data for only one reason
ProfilingDirty bool
}
// NewSource is the preferred method of initialisation for the Source type.
//
// If no ELF file or valid DWARF data can be found in relation to the ROM file
// the function will return nil with an error.
//
// Once the ELF and DWARF file has been identified then Source will always be
// non-nil but with the understanding that the fields may be empty.
func NewSource(romFile string, cart Cartridge, elfFile string) (*Source, error) {
src := &Source{
cart: cart,
path: simplifyPath(filepath.Dir(romFile)),
Instructions: make(map[uint64]*SourceInstruction),
Files: make(map[string]*SourceFile),
Filenames: make([]string, 0, 10),
FilesByShortname: make(map[string]*SourceFile),
ShortFilenames: make([]string, 0, 10),
Functions: make(map[string]*SourceFunction),
FunctionNames: make([]string, 0, 10),
GlobalsByAddress: make(map[uint64]*SourceVariable),
SortedGlobals: SortedVariables{
Variables: make([]*SourceVariable, 0, 100),
},
SortedFunctions: SortedFunctions{
Functions: make([]*SourceFunction, 0, 100),
},
LinesByAddress: make(map[uint64]*SourceLine),
SortedLines: SortedLines{
Lines: make([]*SourceLine, 0, 100),
},
ProfilingDirty: true,
}
var err error
// open ELF file
var ef *elf.File
var fromCartridge bool
if elfFile != "" {
ef, err = elf.Open(elfFile)
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
} else {
ef, fromCartridge = findELF(romFile)
if ef == nil {
return nil, fmt.Errorf("dwarf: compiled ELF file not found")
}
}
defer ef.Close()
// check existance of DWARF data and the DWARF version before proceeding
debug_info := ef.Section(".debug_info")
if debug_info == nil {
return nil, fmt.Errorf("dwarf: ELF file does not have .debug_info section")
}
b, err := debug_info.Data()
if err != nil {
return nil, fmt.Errorf("dwarf: %v", err)
}
version := ef.ByteOrder.Uint16(b[4:])
if version != 4 {
return nil, fmt.Errorf("%w: version %d of DWARF is not supported", UnsupportedDWARF, version)
}
// whether ELF file is isRelocatable or not
isRelocatable := ef.Type&elf.ET_REL == elf.ET_REL
// sanity checks on ELF data only if we've loaded the file ourselves and
// it's not from the cartridge.
if !fromCartridge {
if ef.FileHeader.Machine != elf.EM_ARM {
return nil, fmt.Errorf("dwarf: elf file is not ARM")
}
if ef.FileHeader.Version != elf.EV_CURRENT {
return nil, fmt.Errorf("dwarf: elf file is of unknown version")
}
// big endian byte order is probably fine but we've not tested it
if ef.FileHeader.ByteOrder != binary.LittleEndian {
return nil, fmt.Errorf("dwarf: elf file is not little-endian")
}
// we do not permit relocatable ELF files unless it's been supplied by
// the cartridge. it's not clear what a relocatable ELF file would mean
// in this context so we just disallow it
if isRelocatable {
return nil, fmt.Errorf("dwarf: elf file is relocatable. not permitted for non-ELF cartridges")
}
}
// keeping things simple. only 32bit ELF files supported. 64bit files are
// probably fine but we've not tested them
if ef.Class != elf.ELFCLASS32 {
return nil, fmt.Errorf("dwarf: only 32bit ELF files are supported")
}
// no need to continue if ELF file does not have any DWARF data
dwrf, err := ef.DWARF()
if err != nil {
return nil, fmt.Errorf("dwarf: no DWARF data in ELF file")
}
// addressAdjustment is the value that is added to the addresses in the
// DWARF data to adjust them to the correct value for the emulation
//
// in the case of the relocatable binaries, such as those provided by the
// "ELF" cartridge mapper, the value is taken from the ".text" section. this
// relies on the cartridge mapper supporting the CartCoProcRelocatable
// interface
//
// in the case of non-relocatable binaries the value comes from the
// cartridge mapper if it supports the CartCoProcOrigin interface
var addressAdjustment uint64
// cartridge coprocessor
bus := cart.GetCoProcBus()
if bus == nil {
return nil, fmt.Errorf("dwarf: cartridge has no coprocessor to work with")
}
// acquire origin addresses and debugging sections according to whether the
// cartridge is relocatable or not
if isRelocatable {
c, ok := bus.(coprocessor.CartCoProcRelocatable)
if !ok {
return nil, fmt.Errorf("dwarf: ELF file is reloctable but the cartridge mapper does not support that")
}
if _, o, ok := c.ELFSection(".text"); ok {
addressAdjustment = uint64(o)
} else {
return nil, fmt.Errorf("dwarf: no .text section in ELF file")
}
// always create debugFrame and debugLoc sections even when the
// cartridge doesn't have the corresponding sections. in the case of
// the loclist section this is definitely needed because even without
// .debug_loc data we use the loclistSection to help decode single
// address descriptions (which will definitely be present)
data, _, _ := c.ELFSection(".debug_frame")
src.debugFrame, err = newFrameSection(data, ef.ByteOrder, src.cart.GetCoProcBus().GetCoProc(), nil)
if err != nil {
logger.Logf(logger.Allow, "dwarf", err.Error())
}
data, _, _ = c.ELFSection(".debug_loc")
src.debugLoc, err = newLoclistSection(data, ef.ByteOrder, src.cart.GetCoProcBus().GetCoProc())
if err != nil {
logger.Logf(logger.Allow, "dwarf", err.Error())
}
} else {
c, adjust := bus.(coprocessor.CartCoProcOrigin)
if adjust {
addressAdjustment = uint64(c.ExecutableOrigin())
}
// create frame section from the raw ELF section
rel := frameSectionRelocate{
origin: uint32(addressAdjustment),
}
src.debugFrame, err = newFrameSectionFromFile(ef, src.cart.GetCoProcBus().GetCoProc(), &rel)
if err != nil {
logger.Logf(logger.Allow, "dwarf", err.Error())
}
// create loclist section from the raw ELF section
src.debugLoc, err = newLoclistSectionFromFile(ef, src.cart.GetCoProcBus().GetCoProc())
if err != nil {
logger.Logf(logger.Allow, "dwarf", err.Error())
}
if adjust {
// the addressAdjustment needs further adjustment based on the
// executable section with the lowest address
for _, sec := range ef.Sections {
if sec.Flags&elf.SHF_EXECINSTR == elf.SHF_EXECINSTR {
addressAdjustment = addressAdjustment - sec.Addr
break // for loop
}
}
}
}
// log address adjustment value. how the value was arrived at is slightly
// different depending on whether the ELF file relocatable or not
if addressAdjustment == 0 {
logger.Logf(logger.Allow, "dwarf", "address adjustment not required")
} else {
logger.Logf(logger.Allow, "dwarf", "using address adjustment: %#x", int(addressAdjustment))
}
// disassemble every word in the ELF file using the cartridge coprocessor interface
//
// we could traverse of the progs array of the file here but some ELF files
// that we want to support do not have any program headers. we get the same
// effect by traversing the Sections array and ignoring any section that
// does not have the EXECINSTR flag
for _, sec := range ef.Sections {
if sec.Flags&elf.SHF_EXECINSTR != elf.SHF_EXECINSTR {
continue // for loop
}
// section data
var data []byte
data, err = sec.Data()
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// origin is section address adjusted by both the executable origin and
// the adjustment amount previously recorded
origin := sec.Addr + addressAdjustment
// disassemble section
_ = arm.StaticDisassemble(arm.StaticDisassembleConfig{
Data: data,
Origin: uint32(origin),
ByteOrder: ef.ByteOrder,
Callback: func(e arm.DisasmEntry) {
src.Instructions[uint64(e.Addr)] = &SourceInstruction{
Addr: e.Addr,
opcode: uint32(e.OpcodeHi)<<16 | uint32(e.Opcode),
size: e.Size(),
Disasm: e,
}
},
})
}
bld, err := newBuild(dwrf, src.debugLoc, src.debugFrame)
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// compile units are made up of many files. the files and filenames are in
// the fields below
r := dwrf.Reader()
// loop through file and collate compile units
for {
e, err := r.Next()
if err != nil {
if err == io.EOF {
break // for loop
}
return nil, fmt.Errorf("dwarf: %w", err)
}
if e == nil {
break // for loop
}
if e.Offset == 0 {
continue // for loop
}
switch e.Tag {
case dwarf.TagCompileUnit:
unit := &compileUnit{
unit: e,
children: make(map[dwarf.Offset]*dwarf.Entry),
address: addressAdjustment,
}
fld := e.AttrField(dwarf.AttrLowpc)
if fld != nil {
unit.address = addressAdjustment + uint64(fld.Val.(uint64))
}
// assuming DWARF never has duplicate compile unit entries
src.compileUnits = append(src.compileUnits, unit)
r, err := dwrf.LineReader(e)
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// loop through files in the compilation unit. entry 0 is always nil
for _, f := range r.Files()[1:] {
if _, ok := src.Files[f.Name]; !ok {
sf, err := readSourceFile(f.Name, src.path, &src.AllLines)
if err != nil {
logger.Logf(logger.Allow, "dwarf", "%v", err)
} else {
src.Files[sf.Filename] = sf
src.Filenames = append(src.Filenames, sf.Filename)
src.FilesByShortname[sf.ShortFilename] = sf
src.ShortFilenames = append(src.ShortFilenames, sf.ShortFilename)
}
}
}
fld = e.AttrField(dwarf.AttrProducer)
if fld != nil {
producer := fld.Val.(string)
if strings.HasPrefix(producer, "GNU") {
// check optimisation directive
if strings.Contains(producer, " -O") {
src.Optimised = true
}
}
}
default:
if len(src.compileUnits) == 0 {
return nil, fmt.Errorf("dwarf: bad data: no compile unit tag")
}
src.compileUnits[len(src.compileUnits)-1].children[e.Offset] = e
}
}
// log optimisation message as appropriate
if src.Optimised {
logger.Logf(logger.Allow, "dwarf", "source compiled with optimisation")
}
// build functions from DWARF data
err = bld.buildFunctions(src, addressAdjustment)
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// complete function list with stubs for functions where we don't have any
// DWARF data (but do have symbol data)
addFunctionStubs(src, ef)
// sanity check of functions list
if len(src.Functions) != len(src.FunctionNames) {
return nil, fmt.Errorf("dwarf: unmatched function definitions")
}
// read source lines
err = allocateSourceLines(src, dwrf, addressAdjustment)
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// assign functions to every source line
assignFunctionToSourceLines(src)
// assemble sorted functions list
for _, fn := range src.Functions {
src.SortedFunctions.Functions = append(src.SortedFunctions.Functions, fn)
}
// assemble sorted source lines
//
// we must make sure that we don't duplicate a source line entry: src.Lines
// is indexed by address. however, more than one address may point to a
// single SourceLine
//
// to prevent adding a SourceLine more than once we keep an "observed" map
// indexed by (and this is important) the pointer address of the SourceLine
// and not the execution address
observed := make(map[*SourceLine]bool)
for _, ln := range src.LinesByAddress {
if _, ok := observed[ln]; !ok {
observed[ln] = true
src.SortedLines.Lines = append(src.SortedLines.Lines, ln)
}
}
// build types
err = bld.buildTypes(src)
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// build variables
if relocatable, ok := bus.(coprocessor.CartCoProcRelocatable); ok {
err = bld.buildVariables(src, ef, relocatable, addressAdjustment)
} else {
err = bld.buildVariables(src, ef, nil, addressAdjustment)
}
if err != nil {
return nil, fmt.Errorf("dwarf: %w", err)
}
// sort list of filenames and functions
sort.Strings(src.Filenames)
sort.Strings(src.ShortFilenames)
// sort lines by function and number. sort is stable so we can do this in
// two passes
src.SortedLines.Sort(SortLinesFunction, false, false, false, profiling.FocusAll)
src.SortedLines.Sort(SortLinesNumber, false, false, false, profiling.FocusAll)
// sorted functions
src.SortedFunctions.Sort(SortFunctionsName, false, false, false, profiling.FocusAll)
sort.Strings(src.FunctionNames)
// sorted variables
for _, g := range bld.globals {
src.GlobalsByAddress[g.resolve().address] = g
src.SortedGlobals.Variables = append(src.SortedGlobals.Variables, g)
}
for _, l := range bld.locals {
src.SortedLocals.Variables = append(src.SortedLocals.Variables, l)
}
sort.Sort(src.SortedGlobals)
sort.Sort(src.SortedLocals)
// add children to global and local variables
addVariableChildren(src)
// update global variables
src.UpdateGlobalVariables()
// determine highest address occupied by the program
findHighAddress(src)
// find entry function to the program
findEntryFunction(src)
// log summary
logger.Logf(logger.Allow, "dwarf", "identified %d functions in %d compile units", len(src.Functions), len(src.compileUnits))
logger.Logf(logger.Allow, "dwarf", "%d global variables", len(src.SortedGlobals.Variables))
logger.Logf(logger.Allow, "dwarf", "%d local variable (loclists)", len(src.SortedLocals.Variables))
logger.Logf(logger.Allow, "dwarf", "high address (%08x)", src.HighAddress)
return src, nil
}
func allocateSourceLines(src *Source, dwrf *dwarf.Data, addressAdjustment uint64) error {
for _, e := range src.compileUnits {
// the source line we're working on
var ln *SourceLine
// read every line in the compile unit
r, err := dwrf.LineReader(e.unit)
if err != nil {
return err
}
// the start address of a sequence is reset every time the EndSequence
// flag in a dwarf.LineEntry is set to true
var resetStartAddr bool
var startAddr uint64
// it is implied that the need to reset the start address on the first
// loop iteration
resetStartAddr = true
var le dwarf.LineEntry
for {
err := r.Next(&le)
if err != nil {
if err == io.EOF {
break // line entry for loop. will continue with compile unit loop
}
return err
}
// check that source file has been loaded
if src.Files[le.File.Name] == nil {
logger.Logf(logger.Allow, "dwarf", "file not available for linereader: %s", le.File.Name)
break // line entry for loop. will continue with compile unit loop
}
if le.Line-1 > src.Files[le.File.Name].Content.Len() {
logger.Logf(logger.Allow, "dwarf", "current source is unrelated to ELF/DWARF data (number of lines)")
break // line entry for loop. will continue with compile unit loop
}
// reset start address value if necessary
if resetStartAddr {
startAddr = le.Address + addressAdjustment
resetStartAddr = le.EndSequence
continue
}
// adjust address by executable origin
endAddr := le.Address + addressAdjustment
// sanity check start/end address
if startAddr > endAddr {
return fmt.Errorf("dwarf: allocate source line: start address (%08x) is after end address (%08x)", startAddr, endAddr)
}
// add breakpoint and instruction information to the source line
if ln != nil && endAddr-startAddr > 0 {
// add instruction to source line and add source line to linesByAddress
for addr := startAddr; addr < endAddr; addr++ {
// look for address in list of source instructions
if ins, ok := src.Instructions[addr]; ok {
// add instruction to the list for the source line
ln.Instruction = append(ln.Instruction, ins)
// link source line to instruction
ins.Line = ln
// add source line to list of lines by address
src.LinesByAddress[addr] = ln
// advance address value by opcode size. reduce value by
// one because the loop increment advances by one
// already (which will always apply even if there is no
// instruction for the address)
addr += uint64(ins.size) - 1
}
}
}
// prepare for next iteration
ln = src.Files[le.File.Name].Content.Lines[le.Line-1]
startAddr = endAddr
// if this is the end of a sequence then the start address must be
// reset for the next line item
resetStartAddr = le.EndSequence
}
}
for _, e := range src.compileUnits {
// read every line in the compile unit
r, err := dwrf.LineReader(e.unit)
if err != nil {
return err
}
var le dwarf.LineEntry
for {
err := r.Next(&le)
if err != nil {
if err == io.EOF {
break // line entry for loop. will continue with compile unit loop
}
return err
}
// no need to check whether source file has been loaded because
// we've already checked that on the previous LineReader run
// add breakpoint address to the correct line
if le.IsStmt {
addr := le.Address + addressAdjustment
ln := src.LinesByAddress[addr]
if ln != nil {
ln.BreakAddresses = append(ln.BreakAddresses, uint32(addr))
}
}
}
}
return nil
}
// add children to global and local variables
func addVariableChildren(src *Source) {
for _, g := range src.SortedGlobals.Variables {
g.addVariableChildren(src.debugLoc)
}
for _, l := range src.SortedLocals.Variables {
l.addVariableChildren(src.debugLoc)
}
}
// assign source lines to a function
func assignFunctionToSourceLines(src *Source) {
// for each line in a file compare the address of the first instruction for
// the line to each range in every function. the function with the smallest
// range is the function the line belongs to
for _, sf := range src.Files {
for _, ln := range sf.Content.Lines {
if len(ln.Instruction) > 0 {
var candidateFunction *SourceFunction
var rangeSize uint64
rangeSize = ^uint64(0)
addr := uint64(ln.Instruction[0].Addr)
for _, fn := range src.Functions {
for _, r := range fn.Range {
if addr >= r.Start && addr <= r.End {
if r.End-r.Start < rangeSize {
rangeSize = r.End - r.Start
candidateFunction = fn
break // range loop
}
}
}
}
// we may sometimes reach the end of a loop without having found a corresponding function
if candidateFunction != nil {
ln.Function = candidateFunction
}
}
}
// assign functions to lines that don't have instructions. this method
// isn't great because we can't detect the end of a function, meaning
// that a global variable declared between functions will be wrongly
// allocated
//
// however, this shortcoming can be corrected in the buildVariables()
// function when a global variable is encountered
currentFunction := &SourceFunction{Name: stubIndicator}
for _, ln := range sf.Content.Lines {
if ln.Function.IsStub() {
ln.Function = currentFunction
}
currentFunction = ln.Function
}
}
}
// find entry function to the program
func findEntryFunction(src *Source) {
// TODO: this is a bit of ARM specific knowledge that should be removed
addr, _ := src.cart.GetCoProcBus().GetCoProc().Register(15)
if ln, ok := src.LinesByAddress[uint64(addr)]; ok {
src.MainFunction = ln.Function
return
}
// use function called "main" if it's present. we could add to this list
// other likely names but this would depend on convention, which doesn't
// exist yet (eg. elf_main)
if fn, ok := src.Functions["main"]; ok {
src.MainFunction = fn
return
}
// assume the function of the first line in the source is the entry
// function
for _, ln := range src.SortedLines.Lines {
if len(ln.Instruction) > 0 {
src.MainFunction = ln.Function
break
}
}
if src.MainFunction != nil {
return
}
// if no function can be found for some reason then a stub entry is created
src.MainFunction = CreateStubLine(nil).Function
}
// determine highest address occupied by the program
func findHighAddress(src *Source) {
src.HighAddress = 0
for _, g := range src.SortedGlobals.Variables {
a := g.resolve().address + uint64(g.Type.Size)
if a > src.HighAddress {
src.HighAddress = a
}
}
for _, f := range src.Functions {
for _, r := range f.Range {
if r.End > src.HighAddress {
src.HighAddress = r.End
}
}
}
}
// add function stubs for functions without DWARF data. we do this *after*
// we've looked for functions in the DWARF data (via the line reader) because
// it appears that not every function will necessarily have a symbol and it's
// easier to handle the adding of stubs *after* the the line reader. it does
// mean though that we need to check that a function has not already been added
func addFunctionStubs(src *Source, ef *elf.File) error {
// all the symbols in the ELF file
syms, err := ef.Symbols()
if err != nil {
return err
}
type fn struct {
name string
rng SourceRange
}
var symbolTableFunctions []fn
// the functions from the symbol table
for _, s := range syms {
typ := s.Info & 0x0f
if typ == 0x02 {
// align address
// TODO: this is a bit of ARM specific knowledge that should be removed
a := uint64(s.Value & 0xfffffffe)
symbolTableFunctions = append(symbolTableFunctions, fn{
name: s.Name,
rng: SourceRange{
Start: a,
End: a + uint64(s.Size) - 1,
},
})
}
}
for _, fn := range symbolTableFunctions {
if _, ok := src.Functions[fn.name]; !ok {
// chop off suffix from symbol table name if there is one. not sure
// about this but it neatens things up for the cases I've seen so
// far
fn.name = strings.Split(fn.name, ".")[0]
stubFn := &SourceFunction{
Name: fn.name,
}
stubFn.Range = append(stubFn.Range, fn.rng)
stubFn.DeclLine = CreateStubLine(stubFn)
// add stub function to list of functions but not if the function
// covers an area that has already been seen
addFunction := true
// process all addresses in range, skipping any addresses that we
// already know about from the DWARF data
for a := fn.rng.Start; a <= fn.rng.End; a++ {
if _, ok := src.LinesByAddress[a]; !ok {
src.LinesByAddress[a] = CreateStubLine(stubFn)
} else {
addFunction = false
break
}
}
if addFunction {
if _, ok := src.Functions[stubFn.Name]; !ok {
src.Functions[stubFn.Name] = stubFn
src.FunctionNames = append(src.FunctionNames, stubFn.Name)
}
}
}
}
// add driver function
driverFn := &SourceFunction{
Name: DriverFunctionName,
}
src.Functions[DriverFunctionName] = driverFn
src.FunctionNames = append(src.FunctionNames, DriverFunctionName)
src.DriverSourceLine = CreateStubLine(driverFn)
return nil
}
func readSourceFile(filename string, path string, all *AllSourceLines) (*SourceFile, error) {
var err error
fl := SourceFile{
Filename: filename,
}
// read file data
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
// split files into lines and parse into fragments
var fp fragmentParser
for i, s := range strings.Split(string(b), "\n") {
ln := &SourceLine{
File: &fl,
LineNumber: i + 1, // counting from one
Function: &SourceFunction{
Name: stubIndicator,
},
PlainContent: s,
}
fl.Content.Lines = append(fl.Content.Lines, ln)
fp.parseLine(ln)
// update max line width
if len(s) > fl.Content.MaxLineWidth {
fl.Content.MaxLineWidth = len(s)
}
if len(strings.TrimSpace(s)) > 0 {
all.lines = append(all.lines, ln)
}
}
// evaluate symbolic links for the source filename. path has already been
// processed so the comparison later should work in all instance
filename = simplifyPath(filename)
fl.ShortFilename = longestPath(filename, path)
return &fl, nil
}
func findELF(romFile string) (*elf.File, bool) {
// try the ROM file itself. it might be an ELF file
ef, err := elf.Open(romFile)
if err == nil {
return ef, true
}
// the file is not an ELF file so the remainder of the function will work
// with the path component of the ROM file only
pathToROM := filepath.Dir(romFile)
filenames := []string{
"armcode.elf",
"custom2.elf",
"main.elf",
"ACE_debugging.elf",
}
subpaths := []string{
"",
"main",
filepath.Join("main", "bin"),
filepath.Join("custom", "bin"),
"arm",
}
for _, p := range subpaths {
for _, f := range filenames {
ef, err = elf.Open(filepath.Join(pathToROM, p, f))
if err == nil {
return ef, false
}
}
}
return nil, false
}
// FindSourceLine returns line entry for the address. Returns nil if the
// address has no source line.
func (src *Source) FindSourceLine(addr uint32) *SourceLine {
return src.LinesByAddress[uint64(addr)]
}
// UpdateGlobalVariables using the current state of the emulated coprocessor.
// Local variables are updated when coprocessor yields (see OnYield() function)
func (src *Source) UpdateGlobalVariables() {
for _, varb := range src.SortedGlobals.Variables {
varb.Update()
}
}
// GetLocalVariables retuns the list of local variables for the supplied
// address. Local variables will not be updated.
func (src *Source) GetLocalVariables(ln *SourceLine, addr uint32) []*SourceVariableLocal {
var locals []*SourceVariableLocal
var chosenLocal *SourceVariableLocal
// choose function that covers the smallest (most specific) range in which startAddr
// appears
chosenSize := ^uint64(0)
// function to add chosen local variable to the yield
commitChosen := func() {