fix(parser): read the segment-absolute rendering FS:0
The toolchain's disassembler prints the segment-prefixed disp32 absolute as FS:0, but the bare-name branch read the segment register alone and dropped the offset, so MOVQ FS:0, DX silently encoded a register move. The colon-offset form now lowers to the same segment-absolute operand the 0(FS) spelling takes. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
abc2d32b83
commit
a829b8f321
2 files changed
+63
No files matched your search
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRenderedTextEncodes pins the encoder against the disassembler's
|
||||||
|
// renderings: every line here is a text the toolchain's own decoder prints
|
||||||
|
// for bytes its assembler produced (the disasm package's parity fixtures),
|
||||||
|
// so assembling the printed text must reproduce those bytes. Each row was
|
||||||
|
// read back through the toolchain's decoder; the hex is the fixture's.
|
||||||
|
func TestRenderedTextEncodes(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
text string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// The segment-absolute rendering lowers to the FS-prefixed disp32
|
||||||
|
// absolute, exactly what the 0(FS) spelling encodes.
|
||||||
|
{"MOVQ FS:0, DX", "64488b142500000000"},
|
||||||
|
} {
|
||||||
|
src := "TEXT \u00b7k(SB), NOSPLIT, $0\n\t" + tt.text + "\n\tRET\n"
|
||||||
|
f, errs := parser.Parse("k.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Errorf("%s: parse: %v", tt.text, errs[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: assemble: %v", tt.text, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
code := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||||||
|
// The no-fallthrough rule appends the RET; the pinned instruction is
|
||||||
|
// the prefix.
|
||||||
|
got := hex.EncodeToString(code)
|
||||||
|
if !strings.HasPrefix(got, tt.want) {
|
||||||
|
t.Errorf("%s: bytes %s, want prefix %s", tt.text, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -678,6 +678,21 @@ func parseAddress(g []token.Token) ast.Address {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Segment-absolute in the rendering spelling: FS:0 and GS:0. The
|
||||||
|
// colon-offset form is what the toolchain's disassembler prints for the
|
||||||
|
// segment-prefixed disp32 absolute, so the rendered text must re-encode;
|
||||||
|
// without it the segment register alone parsed and the offset vanished.
|
||||||
|
// The base field naming the segment lowers to the same operand the
|
||||||
|
// 0(FS) spelling takes.
|
||||||
|
if len(g) >= 3 && g[0].Kind == token.Ident && g[1].Kind == token.Colon &&
|
||||||
|
(g[0].Text == "FS" || g[0].Text == "GS") {
|
||||||
|
if v, rest, ok := foldExpr(g[2:]); ok && len(rest) == 0 {
|
||||||
|
addr.Base = g[0].Text
|
||||||
|
addr.Offset = v
|
||||||
|
addr.HasOff = true
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
}
|
||||||
// Bare name (register, label or symbol) possibly with an arm64 shift.
|
// Bare name (register, label or symbol) possibly with an arm64 shift.
|
||||||
if addr.Base == "" && addr.Sym == nil && g[0].Kind == token.Ident {
|
if addr.Base == "" && addr.Sym == nil && g[0].Kind == token.Ident {
|
||||||
sym := &ast.Symbol{Pos: g[0].Pos}
|
sym := &ast.Symbol{Pos: g[0].Pos}
|
||||||
|
|||||||
Reference in new issue
Block a user