feat: llgo monitor -target target -port port
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
69
cmd/internal/monitor/monitor.go
Normal file
69
cmd/internal/monitor/monitor.go
Normal 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
29
cmd/llgo/monitor_cmd.gox
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
9
go.mod
@@ -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
19
go.sum
@@ -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
303
internal/monitor/monitor.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user