Skip to content

Commit b71dea7

Browse files
committed
add settings modal, annotations, fix memory leak, improve UX, improve decompiler
1 parent 631df0d commit b71dea7

22 files changed

Lines changed: 1739 additions & 264 deletions

‎app.go‎

Lines changed: 271 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,81 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
7+
"os"
68

79
"github.com/0xInception/wasmspy/pkg/decompile"
810
"github.com/0xInception/wasmspy/pkg/wasm"
911
"github.com/wailsapp/wails/v2/pkg/runtime"
1012
)
1113

14+
type FunctionAnnotation struct {
15+
Name string `json:"name,omitempty"`
16+
Comment string `json:"comment,omitempty"`
17+
}
18+
19+
type Annotations struct {
20+
Version int `json:"version"`
21+
Functions map[string]*FunctionAnnotation `json:"functions,omitempty"`
22+
Comments map[string]string `json:"comments,omitempty"`
23+
DecompileComments map[string]string `json:"decompileComments,omitempty"`
24+
Bookmarks []uint32 `json:"bookmarks,omitempty"`
25+
}
26+
27+
func NewAnnotations() *Annotations {
28+
return &Annotations{
29+
Version: 1,
30+
Functions: make(map[string]*FunctionAnnotation),
31+
Comments: make(map[string]string),
32+
DecompileComments: make(map[string]string),
33+
Bookmarks: []uint32{},
34+
}
35+
}
36+
37+
func annotationsPath(wasmPath string) string {
38+
return wasmPath + ".wasmspy"
39+
}
40+
41+
func loadAnnotationsFromFile(wasmPath string) *Annotations {
42+
data, err := os.ReadFile(annotationsPath(wasmPath))
43+
if err != nil {
44+
return NewAnnotations()
45+
}
46+
var ann Annotations
47+
if err := json.Unmarshal(data, &ann); err != nil {
48+
return NewAnnotations()
49+
}
50+
if ann.Functions == nil {
51+
ann.Functions = make(map[string]*FunctionAnnotation)
52+
}
53+
if ann.Comments == nil {
54+
ann.Comments = make(map[string]string)
55+
}
56+
if ann.DecompileComments == nil {
57+
ann.DecompileComments = make(map[string]string)
58+
}
59+
return &ann
60+
}
61+
62+
func saveAnnotationsToFile(wasmPath string, ann *Annotations) error {
63+
data, err := json.MarshalIndent(ann, "", " ")
64+
if err != nil {
65+
return err
66+
}
67+
return os.WriteFile(annotationsPath(wasmPath), data, 0644)
68+
}
69+
1270
type App struct {
13-
ctx context.Context
14-
modules map[string]*wasm.ResolvedModule
71+
ctx context.Context
72+
modules map[string]*wasm.ResolvedModule
73+
annotations map[string]*Annotations
1574
}
1675

1776
func NewApp() *App {
1877
return &App{
19-
modules: make(map[string]*wasm.ResolvedModule),
78+
modules: make(map[string]*wasm.ResolvedModule),
79+
annotations: make(map[string]*Annotations),
2080
}
2181
}
2282

@@ -35,6 +95,11 @@ func (a *App) onDomReady(ctx context.Context) {
3595
})
3696
}
3797

98+
func (a *App) SetWindowSize(width, height int) {
99+
runtime.WindowSetSize(a.ctx, width, height)
100+
runtime.WindowCenter(a.ctx)
101+
}
102+
38103
func (a *App) OpenFileDialog() (string, error) {
39104
path, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
40105
Title: "Open WebAssembly Module",
@@ -101,6 +166,7 @@ func (a *App) loadModule(path string, mod *wasm.Module) (*ModuleInfo, error) {
101166
return nil, err
102167
}
103168
a.modules[path] = resolved
169+
a.annotations[path] = loadAnnotationsFromFile(path)
104170

105171
info := &ModuleInfo{}
106172

@@ -130,10 +196,17 @@ func (a *App) loadModule(path string, mod *wasm.Module) (*ModuleInfo, error) {
130196
})
131197
}
132198

199+
ann := a.annotations[path]
133200
for _, fn := range resolved.Functions {
201+
name := fn.Name
202+
if ann != nil {
203+
if fa := ann.Functions[fmt.Sprintf("%d", fn.Index)]; fa != nil && fa.Name != "" {
204+
name = fa.Name
205+
}
206+
}
134207
fi := FunctionInfo{
135208
Index: fn.Index,
136-
Name: fn.Name,
209+
Name: name,
137210
Imported: fn.Imported,
138211
}
139212
if fn.Type != nil {
@@ -174,7 +247,8 @@ func (a *App) DisassembleFunction(path string, index uint32, indent bool) (strin
174247
if fn == nil {
175248
return "", fmt.Errorf("function %d not found", index)
176249
}
177-
return formatFunctionDisasm(fn, module, indent), nil
250+
ann := a.annotations[path]
251+
return formatFunctionDisasm(fn, module, indent, ann), nil
178252
}
179253

180254
func (a *App) GetFunctionWAT(path string, index uint32) (string, error) {
@@ -438,13 +512,29 @@ func getDataSegOffset(instrs []wasm.Instruction) uint32 {
438512
return 0
439513
}
440514

441-
func formatFunctionDisasm(fn *wasm.ResolvedFunction, rm *wasm.ResolvedModule, indented bool) string {
515+
func formatFunctionDisasm(fn *wasm.ResolvedFunction, rm *wasm.ResolvedModule, indented bool, ann *Annotations) string {
442516
if fn.Imported {
443517
return fmt.Sprintf("; imported: %s", fn.Name)
444518
}
445519

520+
name := fn.Name
521+
funcComment := ""
522+
if ann != nil {
523+
if fa := ann.Functions[fmt.Sprintf("%d", fn.Index)]; fa != nil {
524+
if fa.Name != "" {
525+
name = fa.Name
526+
}
527+
if fa.Comment != "" {
528+
funcComment = fa.Comment
529+
}
530+
}
531+
}
532+
446533
var result string
447-
result = fmt.Sprintf("; Function %d: %s\n", fn.Index, fn.Name)
534+
result = fmt.Sprintf("; Function %d: %s\n", fn.Index, name)
535+
if funcComment != "" {
536+
result += fmt.Sprintf("; %s\n", funcComment)
537+
}
448538

449539
if fn.Type != nil {
450540
result += fmt.Sprintf("; Params: %d, Results: %d\n", len(fn.Type.Params), len(fn.Type.Results))
@@ -465,6 +555,13 @@ func formatFunctionDisasm(fn *wasm.ResolvedFunction, rm *wasm.ResolvedModule, in
465555

466556
indent := 0
467557
for _, instr := range fn.Body.Instructions {
558+
comment := ""
559+
if ann != nil {
560+
if c := ann.Comments[fmt.Sprintf("0x%x", instr.Offset)]; c != "" {
561+
comment = " ; " + c
562+
}
563+
}
564+
468565
if indented {
469566
if instr.Opcode == wasm.OpEnd || instr.Opcode == wasm.OpElse {
470567
if indent > 0 {
@@ -476,14 +573,14 @@ func formatFunctionDisasm(fn *wasm.ResolvedFunction, rm *wasm.ResolvedModule, in
476573
for j := 0; j < indent; j++ {
477574
prefix += " "
478575
}
479-
result += fmt.Sprintf("%08x: %s%s\n", instr.Offset, prefix, formatInstrWithImm(&instr))
576+
result += fmt.Sprintf("%08x: %s%s%s\n", instr.Offset, prefix, formatInstrWithImm(&instr), comment)
480577

481578
if instr.Opcode == wasm.OpBlock || instr.Opcode == wasm.OpLoop ||
482579
instr.Opcode == wasm.OpIf || instr.Opcode == wasm.OpElse {
483580
indent++
484581
}
485582
} else {
486-
result += fmt.Sprintf("%08x: %s\n", instr.Offset, formatInstrWithImm(&instr))
583+
result += fmt.Sprintf("%08x: %s%s\n", instr.Offset, formatInstrWithImm(&instr), comment)
487584
}
488585
}
489586
}
@@ -574,3 +671,168 @@ func formatInstr(instr *wasm.Instruction) string {
574671
}
575672
return result
576673
}
674+
675+
func (a *App) GetAnnotations(path string) *Annotations {
676+
if ann := a.annotations[path]; ann != nil {
677+
return ann
678+
}
679+
return NewAnnotations()
680+
}
681+
682+
func (a *App) SetFunctionName(path string, index uint32, name string) error {
683+
ann := a.annotations[path]
684+
if ann == nil {
685+
ann = NewAnnotations()
686+
a.annotations[path] = ann
687+
}
688+
key := fmt.Sprintf("%d", index)
689+
if ann.Functions[key] == nil {
690+
ann.Functions[key] = &FunctionAnnotation{}
691+
}
692+
ann.Functions[key].Name = name
693+
return nil
694+
}
695+
696+
func (a *App) SetFunctionComment(path string, index uint32, comment string) error {
697+
ann := a.annotations[path]
698+
if ann == nil {
699+
ann = NewAnnotations()
700+
a.annotations[path] = ann
701+
}
702+
key := fmt.Sprintf("%d", index)
703+
if ann.Functions[key] == nil {
704+
ann.Functions[key] = &FunctionAnnotation{}
705+
}
706+
ann.Functions[key].Comment = comment
707+
return nil
708+
}
709+
710+
func (a *App) SetOffsetComment(path string, offset uint64, comment string, isDecompile bool) error {
711+
ann := a.annotations[path]
712+
if ann == nil {
713+
ann = NewAnnotations()
714+
a.annotations[path] = ann
715+
}
716+
key := fmt.Sprintf("0x%x", offset)
717+
target := ann.Comments
718+
if isDecompile {
719+
target = ann.DecompileComments
720+
}
721+
if comment == "" {
722+
delete(target, key)
723+
} else {
724+
target[key] = comment
725+
}
726+
return nil
727+
}
728+
729+
func (a *App) SetBookmarks(path string, bookmarks []uint32) error {
730+
ann := a.annotations[path]
731+
if ann == nil {
732+
ann = NewAnnotations()
733+
a.annotations[path] = ann
734+
}
735+
ann.Bookmarks = bookmarks
736+
return nil
737+
}
738+
739+
func (a *App) SaveAnnotations(path string) error {
740+
ann := a.annotations[path]
741+
if ann == nil {
742+
return nil
743+
}
744+
return saveAnnotationsToFile(path, ann)
745+
}
746+
747+
func (a *App) ClearAnnotations(path string) *Annotations {
748+
a.annotations[path] = NewAnnotations()
749+
return a.annotations[path]
750+
}
751+
752+
func (a *App) ExportAnnotations(path string) (string, error) {
753+
ann := a.annotations[path]
754+
if ann == nil {
755+
ann = NewAnnotations()
756+
}
757+
data, err := json.MarshalIndent(ann, "", " ")
758+
if err != nil {
759+
return "", err
760+
}
761+
return string(data), nil
762+
}
763+
764+
func (a *App) ImportAnnotations(path string, data string) error {
765+
var ann Annotations
766+
if err := json.Unmarshal([]byte(data), &ann); err != nil {
767+
return err
768+
}
769+
if ann.Functions == nil {
770+
ann.Functions = make(map[string]*FunctionAnnotation)
771+
}
772+
if ann.Comments == nil {
773+
ann.Comments = make(map[string]string)
774+
}
775+
if ann.DecompileComments == nil {
776+
ann.DecompileComments = make(map[string]string)
777+
}
778+
a.annotations[path] = &ann
779+
return nil
780+
}
781+
782+
func (a *App) ExportAnnotationsToFile(wasmPath string) (string, error) {
783+
savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
784+
Title: "Export Annotations",
785+
DefaultFilename: "annotations.wasmspy",
786+
Filters: []runtime.FileFilter{
787+
{DisplayName: "WasmSpy Annotations", Pattern: "*.wasmspy"},
788+
{DisplayName: "JSON Files", Pattern: "*.json"},
789+
},
790+
})
791+
if err != nil || savePath == "" {
792+
return "", err
793+
}
794+
ann := a.annotations[wasmPath]
795+
if ann == nil {
796+
ann = NewAnnotations()
797+
}
798+
data, err := json.MarshalIndent(ann, "", " ")
799+
if err != nil {
800+
return "", err
801+
}
802+
if err := os.WriteFile(savePath, data, 0644); err != nil {
803+
return "", err
804+
}
805+
return savePath, nil
806+
}
807+
808+
func (a *App) ImportAnnotationsFromFile(wasmPath string) (string, error) {
809+
openPath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
810+
Title: "Import Annotations",
811+
Filters: []runtime.FileFilter{
812+
{DisplayName: "WasmSpy Annotations", Pattern: "*.wasmspy"},
813+
{DisplayName: "JSON Files", Pattern: "*.json"},
814+
},
815+
})
816+
if err != nil || openPath == "" {
817+
return "", err
818+
}
819+
data, err := os.ReadFile(openPath)
820+
if err != nil {
821+
return "", err
822+
}
823+
var ann Annotations
824+
if err := json.Unmarshal(data, &ann); err != nil {
825+
return "", err
826+
}
827+
if ann.Functions == nil {
828+
ann.Functions = make(map[string]*FunctionAnnotation)
829+
}
830+
if ann.Comments == nil {
831+
ann.Comments = make(map[string]string)
832+
}
833+
if ann.DecompileComments == nil {
834+
ann.DecompileComments = make(map[string]string)
835+
}
836+
a.annotations[wasmPath] = &ann
837+
return openPath, nil
838+
}

0 commit comments

Comments
 (0)