-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
Copy pathassembly.go
147 lines (133 loc) · 4.46 KB
/
assembly.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
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package golang
// This file produces the "Browse GOARCH assembly of f" HTML report.
//
// See also:
// - ./codeaction.go - computes the symbol and offers the CodeAction command.
// - ../server/command.go - handles the command by opening a web page.
// - ../server/server.go - handles the HTTP request and calls this function.
//
// For language-server behavior in Go assembly language files,
// see [golang.org/x/tools/gopls/internal/goasm].
import (
"bytes"
"context"
"fmt"
"html"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"golang.org/x/tools/gopls/internal/cache"
"golang.org/x/tools/gopls/internal/util/morestrings"
)
// AssemblyHTML returns an HTML document containing an assembly listing of the selected function.
//
// TODO(adonovan): cross-link jumps and block labels, like github.com/aclements/objbrowse.
//
// See gopls/internal/test/integration/misc/webserver_test.go for tests.
func AssemblyHTML(ctx context.Context, snapshot *cache.Snapshot, w http.ResponseWriter, pkg *cache.Package, symbol string, web Web) {
// Prepare to compile the package with -S, and capture its stderr stream.
inv, cleanupInvocation, err := snapshot.GoCommandInvocation(cache.NoNetwork, pkg.Metadata().CompiledGoFiles[0].DirPath(), "build", []string{"-gcflags=-S", "."})
if err != nil {
// e.g. failed to write overlays (rare)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer cleanupInvocation()
escape := html.EscapeString
// Emit the start of the report.
title := fmt.Sprintf("%s assembly for %s",
escape(snapshot.View().GOARCH()),
escape(symbol))
io.WriteString(w, `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>`+escape(title)+`</title>
<link rel="stylesheet" href="/assets/common.css">
<script src="/assets/common.js"></script>
</head>
<body>
<h1>`+title+`</h1>
<p>
<a href='https://go.dev/doc/asm'>A Quick Guide to Go's Assembler</a>
</p>
<p>
Experimental. <a href='https://github.com/golang/go/issues/67478'>Contributions welcome!</a>
</p>
<p>
Click on a source line marker <code>L1234</code> to navigate your editor there.
(VS Code users: please upvote <a href='https://github.com/microsoft/vscode/issues/208093'>#208093</a>)
</p>
<p id='compiling'>Compiling...</p>
<pre>
`)
if flusher, ok := w.(http.Flusher); ok {
flusher.Flush()
}
// At this point errors must be reported by writing HTML.
// To do this, set "status" return early.
var buf bytes.Buffer
status := "Reload the page to recompile."
defer func() {
// Update the "Compiling..." message.
fmt.Fprintf(&buf, `
</pre>
<script>
document.getElementById('compiling').innerText = %q;
</script>
</body>`, status)
w.Write(buf.Bytes())
}()
// Compile the package.
_, stderr, err, _ := snapshot.View().GoCommandRunner().RunRaw(ctx, *inv)
if err != nil {
status = fmt.Sprintf("compilation failed: %v", err)
return
}
// Write the rest of the report.
content := stderr.String()
// insnRx matches an assembly instruction line.
// Submatch groups are: (offset-hex-dec, file-line-column, instruction).
insnRx := regexp.MustCompile(`^(\s+0x[0-9a-f ]+)\(([^)]*)\)\s+(.*)$`)
// Parse the functions of interest out of the listing.
// Each function is of the form:
//
// symbol STEXT k=v...
// 0x0000 00000 (/file.go:123) NOP...
// ...
//
// Allow matches of symbol, symbol.func1, symbol.deferwrap, etc.
on := false
for line := range strings.SplitSeq(content, "\n") {
// start of function symbol?
if strings.Contains(line, " STEXT ") {
on = strings.HasPrefix(line, symbol) &&
(line[len(symbol)] == ' ' || line[len(symbol)] == '.')
}
if !on {
continue // within uninteresting symbol
}
// In lines of the form
// "\t0x0000 00000 (/file.go:123) NOP..."
// replace the "(/file.go:123)" portion with an "L0123" source link.
// Skip filenames of the form "<foo>".
if parts := insnRx.FindStringSubmatch(line); parts != nil {
link := " " // if unknown
if file, linenum, ok := morestrings.CutLast(parts[2], ":"); ok && !strings.HasPrefix(file, "<") {
if linenum, err := strconv.Atoi(linenum); err == nil {
text := fmt.Sprintf("L%04d", linenum)
link = sourceLink(text, web.SrcURL(file, linenum, 1))
}
}
fmt.Fprintf(&buf, "%s\t%s\t%s", escape(parts[1]), link, escape(parts[3]))
} else {
buf.WriteString(escape(line))
}
buf.WriteByte('\n')
}
}