mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
2e76cd3687
This shows nicely formatted error messages for missing symbol names and for out-of-flash, out-of-RAM conditions (on microcontrollers with limited flash/RAM). Unfortunately the missing symbol name errors aren't available on Windows and WebAssembly because the linker doesn't report source locations yet. This is something that I could perhaps improve in LLD.
163 lines
4.5 KiB
Go
163 lines
4.5 KiB
Go
package builder
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/scanner"
|
|
"go/token"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// runCCompiler invokes a C compiler with the given arguments.
|
|
func runCCompiler(flags ...string) error {
|
|
if hasBuiltinTools {
|
|
// Compile this with the internal Clang compiler.
|
|
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
}
|
|
|
|
// Compile this with an external invocation of the Clang compiler.
|
|
return execCommand("clang", flags...)
|
|
}
|
|
|
|
// link invokes a linker with the given name and flags.
|
|
func link(linker string, flags ...string) error {
|
|
// We only support LLD.
|
|
if linker != "ld.lld" && linker != "wasm-ld" {
|
|
return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker)
|
|
}
|
|
|
|
var cmd *exec.Cmd
|
|
if hasBuiltinTools {
|
|
cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...)
|
|
} else {
|
|
name, err := LookupCommand(linker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd = exec.Command(name, flags...)
|
|
}
|
|
var buf bytes.Buffer
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = &buf
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
if buf.Len() == 0 {
|
|
// The linker failed but ther was no output.
|
|
// Therefore, show some output anyway.
|
|
return fmt.Errorf("failed to run linker: %w", err)
|
|
}
|
|
return parseLLDErrors(buf.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Split LLD errors into individual erros (including errors that continue on the
|
|
// next line, using a ">>>" prefix). If possible, replace the raw errors with a
|
|
// more user-friendly version (and one that's more in a Go style).
|
|
func parseLLDErrors(text string) error {
|
|
// Split linker output in separate error messages.
|
|
lines := strings.Split(text, "\n")
|
|
var errorLines []string // one or more line (belonging to a single error) per line
|
|
for _, line := range lines {
|
|
line = strings.TrimRight(line, "\r") // needed for Windows
|
|
if len(errorLines) != 0 && strings.HasPrefix(line, ">>> ") {
|
|
errorLines[len(errorLines)-1] += "\n" + line
|
|
continue
|
|
}
|
|
if line == "" {
|
|
continue
|
|
}
|
|
errorLines = append(errorLines, line)
|
|
}
|
|
|
|
// Parse error messages.
|
|
var linkErrors []error
|
|
var flashOverflow, ramOverflow uint64
|
|
for _, message := range errorLines {
|
|
parsedError := false
|
|
|
|
// Check for undefined symbols.
|
|
// This can happen in some cases like with CGo and //go:linkname tricker.
|
|
if matches := regexp.MustCompile(`^ld.lld: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
|
|
symbolName := matches[1]
|
|
for _, line := range strings.Split(message, "\n") {
|
|
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
|
|
if matches != nil {
|
|
parsedError = true
|
|
line, _ := strconv.Atoi(matches[3])
|
|
// TODO: detect common mistakes like -gc=none?
|
|
linkErrors = append(linkErrors, scanner.Error{
|
|
Pos: token.Position{
|
|
Filename: matches[2],
|
|
Line: line,
|
|
},
|
|
Msg: "linker could not find symbol " + symbolName,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for flash/RAM overflow.
|
|
if matches := regexp.MustCompile(`^ld.lld: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil {
|
|
region := matches[2]
|
|
n, err := strconv.ParseUint(matches[3], 10, 64)
|
|
if err != nil {
|
|
// Should not happen at all (unless it overflows an uint64 for some reason).
|
|
continue
|
|
}
|
|
|
|
// Check which area overflowed.
|
|
// Some chips use differently named memory areas, but these are by
|
|
// far the most common.
|
|
switch region {
|
|
case "FLASH_TEXT":
|
|
if n > flashOverflow {
|
|
flashOverflow = n
|
|
}
|
|
parsedError = true
|
|
case "RAM":
|
|
if n > ramOverflow {
|
|
ramOverflow = n
|
|
}
|
|
parsedError = true
|
|
}
|
|
}
|
|
|
|
// If we couldn't parse the linker error: show the error as-is to
|
|
// the user.
|
|
if !parsedError {
|
|
linkErrors = append(linkErrors, LinkerError{message})
|
|
}
|
|
}
|
|
|
|
if flashOverflow > 0 {
|
|
linkErrors = append(linkErrors, LinkerError{
|
|
Msg: fmt.Sprintf("program too large for this chip (flash overflowed by %d bytes)\n\toptimization guide: https://tinygo.org/docs/guides/optimizing-binaries/", flashOverflow),
|
|
})
|
|
}
|
|
if ramOverflow > 0 {
|
|
linkErrors = append(linkErrors, LinkerError{
|
|
Msg: fmt.Sprintf("program uses too much static RAM on this chip (RAM overflowed by %d bytes)", ramOverflow),
|
|
})
|
|
}
|
|
|
|
return newMultiError(linkErrors, "")
|
|
}
|
|
|
|
// LLD linker error that could not be parsed or doesn't refer to a source
|
|
// location.
|
|
type LinkerError struct {
|
|
Msg string
|
|
}
|
|
|
|
func (e LinkerError) Error() string {
|
|
return e.Msg
|
|
}
|