feat: llgo monitor -target target -port port

This commit is contained in:
Li Jie
2025-09-06 21:29:48 +08:00
parent 519faabfe1
commit 9a5b231c88
8 changed files with 464 additions and 10 deletions

View File

@@ -21,6 +21,7 @@ var Tags string
var Target string var Target string
var Emulator bool var Emulator bool
var Port string var Port string
var BaudRate int
var AbiMode int var AbiMode int
var CheckLinkArgs bool var CheckLinkArgs bool
var CheckLLFiles bool var CheckLLFiles bool
@@ -30,7 +31,6 @@ func AddBuildFlags(fs *flag.FlagSet) {
fs.BoolVar(&Verbose, "v", false, "Verbose mode") fs.BoolVar(&Verbose, "v", false, "Verbose mode")
fs.StringVar(&Tags, "tags", "", "Build tags") fs.StringVar(&Tags, "tags", "", "Build tags")
fs.StringVar(&BuildEnv, "buildenv", "", "Build environment") fs.StringVar(&BuildEnv, "buildenv", "", "Build environment")
fs.StringVar(&Target, "target", "", "Target platform (e.g., rp2040, wasi)")
if buildenv.Dev { if buildenv.Dev {
fs.IntVar(&AbiMode, "abi", 2, "ABI mode (default 2). 0 = none, 1 = cfunc, 2 = allfunc.") fs.IntVar(&AbiMode, "abi", 2, "ABI mode (default 2). 0 = none, 1 = cfunc, 2 = allfunc.")
fs.BoolVar(&CheckLinkArgs, "check-linkargs", false, "check link args valid") fs.BoolVar(&CheckLinkArgs, "check-linkargs", false, "check link args valid")
@@ -46,7 +46,9 @@ func AddEmulatorFlags(fs *flag.FlagSet) {
} }
func AddEmbeddedFlags(fs *flag.FlagSet) { func AddEmbeddedFlags(fs *flag.FlagSet) {
fs.StringVar(&Target, "target", "", "Target platform (e.g., rp2040, wasi)")
fs.StringVar(&Port, "port", "", "Target port for flashing") fs.StringVar(&Port, "port", "", "Target port for flashing")
fs.IntVar(&BaudRate, "baudrate", 115200, "Baudrate for serial communication")
} }
func AddCmpTestFlags(fs *flag.FlagSet) { func AddCmpTestFlags(fs *flag.FlagSet) {
@@ -57,18 +59,18 @@ func UpdateConfig(conf *build.Config) {
conf.Tags = Tags conf.Tags = Tags
conf.Verbose = Verbose conf.Verbose = Verbose
conf.Target = Target conf.Target = Target
conf.Port = Port
conf.BaudRate = BaudRate
switch conf.Mode { switch conf.Mode {
case build.ModeBuild: case build.ModeBuild:
conf.OutFile = OutputFile conf.OutFile = OutputFile
conf.FileFormat = FileFormat conf.FileFormat = FileFormat
case build.ModeRun, build.ModeTest: case build.ModeRun, build.ModeTest:
conf.Emulator = Emulator conf.Emulator = Emulator
conf.Port = Port
case build.ModeInstall: case build.ModeInstall:
conf.Port = Port
case build.ModeCmpTest: case build.ModeCmpTest:
conf.Emulator = Emulator conf.Emulator = Emulator
conf.Port = Port
conf.GenExpect = Gen conf.GenExpect = Gen
} }
if buildenv.Dev { if buildenv.Dev {

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package monitor
import (
"fmt"
"os"
"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/cmd/internal/flags"
"github.com/goplus/llgo/internal/monitor"
)
// Cmd represents the monitor command.
var Cmd = &base.Command{
UsageLine: "llgo monitor [flags] [executable]",
Short: "Monitor serial output from device",
}
func init() {
flags.AddEmbeddedFlags(&Cmd.Flag)
Cmd.Run = runMonitor
}
func runMonitor(cmd *base.Command, args []string) {
cmd.Flag.Parse(args)
args = cmd.Flag.Args()
if len(args) > 1 {
fmt.Fprintf(os.Stderr, "llgo monitor: too many arguments\n")
os.Exit(1)
}
if flags.Port == "" && flags.Target == "" {
fmt.Fprintf(os.Stderr, "llgo monitor: must specify either -port or -target\n")
return
}
var executable string
if len(args) == 1 {
executable = args[0]
}
config := monitor.MonitorConfig{
Port: flags.Port,
Target: flags.Target,
BaudRate: flags.BaudRate,
Executable: executable,
}
if err := monitor.Monitor(config, true); err != nil {
fmt.Fprintf(os.Stderr, "llgo monitor: %v\n", err)
os.Exit(1)
}
}

29
cmd/llgo/monitor_cmd.gox Normal file
View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import (
self "github.com/goplus/llgo/cmd/internal/monitor"
)
use "monitor [flags] [executable]"
short "Monitor serial output from device"
flagOff
run args => {
self.Cmd.Run self.Cmd, args
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/goplus/llgo/cmd/internal/build" "github.com/goplus/llgo/cmd/internal/build"
"github.com/goplus/llgo/cmd/internal/clean" "github.com/goplus/llgo/cmd/internal/clean"
"github.com/goplus/llgo/cmd/internal/install" "github.com/goplus/llgo/cmd/internal/install"
"github.com/goplus/llgo/cmd/internal/monitor"
"github.com/goplus/llgo/cmd/internal/run" "github.com/goplus/llgo/cmd/internal/run"
"github.com/goplus/llgo/cmd/internal/test" "github.com/goplus/llgo/cmd/internal/test"
"github.com/goplus/llgo/internal/env" "github.com/goplus/llgo/internal/env"
@@ -40,6 +41,10 @@ type Cmd_install struct {
type App struct { type App struct {
xcmd.App xcmd.App
} }
type Cmd_monitor struct {
xcmd.Command
*App
}
type Cmd_run struct { type Cmd_run struct {
xcmd.Command xcmd.Command
*App *App
@@ -64,10 +69,11 @@ func (this *App) Main() {
_xgo_obj2 := &Cmd_cmptest{App: this} _xgo_obj2 := &Cmd_cmptest{App: this}
_xgo_obj3 := &Cmd_get{App: this} _xgo_obj3 := &Cmd_get{App: this}
_xgo_obj4 := &Cmd_install{App: this} _xgo_obj4 := &Cmd_install{App: this}
_xgo_obj5 := &Cmd_run{App: this} _xgo_obj5 := &Cmd_monitor{App: this}
_xgo_obj6 := &Cmd_test{App: this} _xgo_obj6 := &Cmd_run{App: this}
_xgo_obj7 := &Cmd_version{App: this} _xgo_obj7 := &Cmd_test{App: this}
xcmd.Gopt_App_Main(this, _xgo_obj0, _xgo_obj1, _xgo_obj2, _xgo_obj3, _xgo_obj4, _xgo_obj5, _xgo_obj6, _xgo_obj7) _xgo_obj8 := &Cmd_version{App: this}
xcmd.Gopt_App_Main(this, _xgo_obj0, _xgo_obj1, _xgo_obj2, _xgo_obj3, _xgo_obj4, _xgo_obj5, _xgo_obj6, _xgo_obj7, _xgo_obj8)
} }
//line cmd/llgo/build_cmd.gox:20 //line cmd/llgo/build_cmd.gox:20
@@ -163,6 +169,25 @@ func (this *Cmd_install) Classfname() string {
return "install" return "install"
} }
//line cmd/llgo/monitor_cmd.gox:21
func (this *Cmd_monitor) Main(_xgo_arg0 string) {
this.Command.Main(_xgo_arg0)
//line cmd/llgo/monitor_cmd.gox:21:1
this.Use("monitor [flags] [executable]")
//line cmd/llgo/monitor_cmd.gox:23:1
this.Short("Monitor serial output from device")
//line cmd/llgo/monitor_cmd.gox:25:1
this.FlagOff()
//line cmd/llgo/monitor_cmd.gox:27:1
this.Run__1(func(args []string) {
//line cmd/llgo/monitor_cmd.gox:28:1
monitor.Cmd.Run(monitor.Cmd, args)
})
}
func (this *Cmd_monitor) Classfname() string {
return "monitor"
}
//line cmd/llgo/run_cmd.gox:20 //line cmd/llgo/run_cmd.gox:20
func (this *Cmd_run) Main(_xgo_arg0 string) { func (this *Cmd_run) Main(_xgo_arg0 string) {
this.Command.Main(_xgo_arg0) this.Command.Main(_xgo_arg0)

View File

@@ -19,7 +19,7 @@ Compile program to output file.
### llgo run ### llgo run
Compile and run program. Compile and run program.
- No `-target`: Run locally - No `-target`: Run locally
- With `-target`: Run on device or emulator - With `-target`: Run on device or emulator (equivalent to `install` + `monitor`)
### llgo test ### llgo test
Compile and run tests. Compile and run tests.

9
go.mod
View File

@@ -15,11 +15,18 @@ require (
golang.org/x/tools v0.36.0 golang.org/x/tools v0.36.0
) )
require github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 require (
github.com/mattn/go-tty v0.0.7
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
go.bug.st/serial v1.6.4
)
require ( require (
github.com/creack/goselect v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.27.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
) )
replace github.com/goplus/llgo/runtime => ./runtime replace github.com/goplus/llgo/runtime => ./runtime

19
go.sum
View File

@@ -1,3 +1,7 @@
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/goplus/cobra v1.9.12 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s= github.com/goplus/cobra v1.9.12 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s=
@@ -10,13 +14,28 @@ github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=
github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94= github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94=
github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50= github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qiniu/x v1.15.1 h1:avE+YQaowp8ZExjylOeSM73rUo3MQKBAYVxh4NJ8dY8= github.com/qiniu/x v1.15.1 h1:avE+YQaowp8ZExjylOeSM73rUo3MQKBAYVxh4NJ8dY8=
github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII= github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I= github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I=
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

303
internal/monitor/monitor.go Normal file
View File

@@ -0,0 +1,303 @@
package monitor
import (
"debug/dwarf"
"debug/elf"
"debug/macho"
"debug/pe"
"errors"
"fmt"
"go/token"
"io"
"os"
"os/signal"
"regexp"
"strconv"
"time"
"github.com/goplus/llgo/internal/crosscompile"
"github.com/mattn/go-tty"
"go.bug.st/serial"
)
// MonitorConfig contains configuration for the monitor
type MonitorConfig struct {
Port string // Serial port device
Target string // Target name for crosscompile config
BaudRate int // Baudrate of serial monitor
Executable string // Optional path to executable for debug info
WaitTime int // Wait time for port connection (ms)
}
// Monitor starts serial monitoring with the given configuration
func Monitor(config MonitorConfig, verbose bool) error {
// Set defaults
if config.BaudRate == 0 {
config.BaudRate = 115200
}
if config.WaitTime == 0 {
config.WaitTime = 300
}
// If target is specified, try to get port from crosscompile config
if config.Target != "" && config.Port == "" {
port, err := getPortFromTarget(config.Target)
if err != nil && verbose {
fmt.Fprintf(os.Stderr, "Warning: could not get port from target: %v\n", err)
}
if port != "" {
config.Port = port
}
}
if config.Port == "" {
return fmt.Errorf("port not specified and could not determine from target")
}
if verbose {
fmt.Fprintf(os.Stderr, "Connecting to %s at %d baud\n", config.Port, config.BaudRate)
}
// Open serial port with retry
var serialConn serial.Port
var err error
for i := 0; i <= config.WaitTime; i++ {
serialConn, err = serial.Open(config.Port, &serial.Mode{BaudRate: config.BaudRate})
if err != nil {
if i < config.WaitTime {
time.Sleep(10 * time.Millisecond)
continue
}
return fmt.Errorf("failed to open port %s: %w", config.Port, err)
}
break
}
defer serialConn.Close()
// Open TTY for input
tty, err := tty.Open()
if err != nil {
return fmt.Errorf("failed to open TTY: %w", err)
}
defer tty.Close()
// Setup signal handling
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
defer signal.Stop(sig)
// Create output writer with optional debug info
var writer *outputWriter
if config.Executable != "" {
writer = newOutputWriter(os.Stdout, config.Executable)
} else {
writer = newOutputWriter(os.Stdout, "")
}
fmt.Printf("Connected to %s. Press Ctrl-C to exit.\n", config.Port)
errCh := make(chan error, 2)
// Goroutine for reading from serial port
go func() {
buf := make([]byte, 100*1024)
for {
n, err := serialConn.Read(buf)
if err != nil {
errCh <- fmt.Errorf("serial read error: %w", err)
return
}
writer.Write(buf[:n])
}
}()
// Goroutine for reading from TTY and writing to serial port
go func() {
for {
r, err := tty.ReadRune()
if err != nil {
errCh <- fmt.Errorf("TTY read error: %w", err)
return
}
if r == 0 {
continue
}
serialConn.Write([]byte(string(r)))
}
}()
// Wait for signal or error
select {
case <-sig:
if verbose {
fmt.Fprintf(os.Stderr, "\nDisconnected from %s\n", config.Port)
}
return nil
case err := <-errCh:
return err
}
}
// getPortFromTarget tries to get serial port from target configuration
func getPortFromTarget(target string) (string, error) {
export, err := crosscompile.Use("", "", false, target)
if err != nil {
return "", err
}
// Try to get port from serial port list
if len(export.Flash.SerialPort) > 0 {
return export.Flash.SerialPort[0], nil
}
return "", fmt.Errorf("no serial port found in target configuration")
}
var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
// Extract the address from the "panic: runtime error at" message.
func extractPanicAddress(line []byte) uint64 {
matches := addressMatch.FindSubmatch(line)
if matches != nil {
address, err := strconv.ParseUint(string(matches[1]), 16, 64)
if err == nil {
return address
}
}
return 0
}
// Convert an address in the binary to a source address location.
func addressToLine(executable string, address uint64) (token.Position, error) {
data, err := readDWARF(executable)
if err != nil {
return token.Position{}, err
}
r := data.Reader()
for {
e, err := r.Next()
if err != nil {
return token.Position{}, err
}
if e == nil {
break
}
switch e.Tag {
case dwarf.TagCompileUnit:
r.SkipChildren()
lr, err := data.LineReader(e)
if err != nil {
return token.Position{}, err
}
var lineEntry = dwarf.LineEntry{
EndSequence: true,
}
for {
// Read the next .debug_line entry.
prevLineEntry := lineEntry
err := lr.Next(&lineEntry)
if err != nil {
if err == io.EOF {
break
}
return token.Position{}, err
}
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
// Tombstone value. This symbol has been removed, for
// example by the --gc-sections linker flag. It is still
// here in the debug information because the linker can't
// just remove this reference.
// Read until the next EndSequence so that this sequence is
// skipped.
// For more details, see (among others):
// https://reviews.llvm.org/D84825
for {
err := lr.Next(&lineEntry)
if err != nil {
return token.Position{}, err
}
if lineEntry.EndSequence {
break
}
}
}
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
if prevLineEntry.Address <= address && lineEntry.Address > address {
return token.Position{
Filename: prevLineEntry.File.Name,
Line: prevLineEntry.Line,
Column: prevLineEntry.Column,
}, nil
}
}
}
}
}
return token.Position{}, nil // location not found
}
// Read the DWARF debug information from a given file (in various formats).
func readDWARF(executable string) (*dwarf.Data, error) {
f, err := os.Open(executable)
if err != nil {
return nil, err
}
defer f.Close()
if file, err := elf.NewFile(f); err == nil {
return file.DWARF()
} else if file, err := macho.NewFile(f); err == nil {
return file.DWARF()
} else if file, err := pe.NewFile(f); err == nil {
return file.DWARF()
} else {
return nil, errors.New("unknown binary format")
}
}
type outputWriter struct {
out io.Writer
executable string
line []byte
}
// newOutputWriter returns an io.Writer that will intercept panic addresses and
// will try to insert a source location in the output if the source location can
// be found in the executable.
func newOutputWriter(out io.Writer, executable string) *outputWriter {
return &outputWriter{
out: out,
executable: executable,
}
}
func (w *outputWriter) Write(p []byte) (n int, err error) {
start := 0
for i, c := range p {
if c == '\n' {
w.out.Write(p[start : i+1])
start = i + 1
if w.executable != "" {
address := extractPanicAddress(w.line)
if address != 0 {
loc, err := addressToLine(w.executable, address)
if err == nil && loc.Filename != "" {
fmt.Printf("[llgo: panic at %s]\n", loc.String())
}
}
}
w.line = w.line[:0]
} else {
w.line = append(w.line, c)
}
}
w.out.Write(p[start:])
n = len(p)
return
}