@@ -175,6 +175,20 @@ type Disassembler interface {
175175 Name () string
176176}
177177
178+ // MaxInstLen returns the longest instruction encoding (in bytes) for an
179+ // architecture, used to size the byte column in disassembly views. Fixed-length
180+ // RISC ISAs need only their word size, so their column is much narrower than the
181+ // variable-length x86 cap; an unknown arch falls back to the x86 cap.
182+ func MaxInstLen (a Arch ) int {
183+ switch a {
184+ case ArchARM64 , ArchARM , ArchPPC64 , ArchPPC64LE , ArchPPC , ArchPPCLE , ArchLoong64 , ArchRISCV64 :
185+ return 4 // fixed 4 bytes (RISC-V's compressed forms are 2, still ≤ 4)
186+ case ArchS390X :
187+ return 6 // 2, 4 or 6
188+ }
189+ return 8 // x86/x86-64 are variable-length; cap the column as before
190+ }
191+
178192// For returns a single-instruction decoder for a supported architecture.
179193func For (a Arch ) (Disassembler , error ) {
180194 switch a {
@@ -271,7 +285,7 @@ func (arm64d) Decode(code []byte, addr uint64) (Inst, error) {
271285 if err != nil {
272286 return Inst {}, err
273287 }
274- text := resolveRelTargets (arm64asm .GNUSyntax (inst ), addr )
288+ text := hexImmediates ( resolveRelTargets (arm64asm .GNUSyntax (inst ), addr ) )
275289 return Inst {Addr : addr , Bytes : code [:4 ], Text : text , Class : Classify (text )}, nil
276290}
277291
@@ -373,7 +387,7 @@ func (armd) Decode(code []byte, addr uint64) (out Inst, err error) {
373387 if n == 0 || n > len (code ) {
374388 n = 4
375389 }
376- text := resolveRelTargets (armasm .GNUSyntax (inst ), addr )
390+ text := hexImmediates ( resolveRelTargets (armasm .GNUSyntax (inst ), addr ) )
377391 return Inst {Addr : addr , Bytes : code [:n ], Text : text , Class : Classify (text )}, nil
378392}
379393
@@ -478,6 +492,12 @@ func (loong64d) Decode(code []byte, addr uint64) (out Inst, err error) {
478492// signed displacement (e.g. ".+0xfffffffffffffc58" is a negative jump); uint64
479493// wraparound makes "addr + value" land on the right byte either way.
480494func resolveRelTargets (text string , addr uint64 ) string {
495+ // Fast path: only branch/PC-relative operands carry the ".+0x"/".-0x" form, so
496+ // the vast majority of instructions need no rewrite — skip the allocation.
497+ if ! strings .Contains (text , ".+0x" ) && ! strings .Contains (text , ".-0x" ) &&
498+ ! strings .Contains (text , ".+0X" ) && ! strings .Contains (text , ".-0X" ) {
499+ return text
500+ }
481501 var b strings.Builder
482502 for i := 0 ; i < len (text ); {
483503 if text [i ] == '.' && i + 3 < len (text ) &&
@@ -494,7 +514,7 @@ func resolveRelTargets(text string, addr uint64) string {
494514 if text [i + 1 ] == '-' {
495515 target = addr - v
496516 }
497- fmt . Fprintf (& b , "0x%x" , target )
517+ appendHexTo (& b , target )
498518 i = j
499519 continue
500520 }
@@ -506,6 +526,66 @@ func resolveRelTargets(text string, addr uint64) string {
506526 return b .String ()
507527}
508528
529+ // hexImmediates rewrites the decimal immediates the ARM/ARM64 GNU syntaxers print
530+ // (e.g. memory offsets "[sp,#8]", "[sp,#-16]") into hex, so they read like objdump
531+ // ("[sp,#0x8]", "[sp,#-0x10]"). Immediates already in hex ("#0x40"), floats
532+ // ("#1.0") and any non-"#" use are left untouched; x86 immediates use "$" and are
533+ // already hex, so only the ARM-family decoders call this. The fast path returns
534+ // the input unchanged (no allocation) when there is no "#" to rewrite.
535+ func hexImmediates (s string ) string {
536+ if strings .IndexByte (s , '#' ) < 0 {
537+ return s
538+ }
539+ var b strings.Builder
540+ b .Grow (len (s ) + 8 )
541+ for i := 0 ; i < len (s ); {
542+ if s [i ] != '#' {
543+ b .WriteByte (s [i ])
544+ i ++
545+ continue
546+ }
547+ j := i + 1
548+ neg := false
549+ if j < len (s ) && (s [j ] == '-' || s [j ] == '+' ) {
550+ neg = s [j ] == '-'
551+ j ++
552+ }
553+ start := j
554+ for j < len (s ) && s [j ] >= '0' && s [j ] <= '9' {
555+ j ++
556+ }
557+ // Leave it alone unless this is a plain decimal: no digits, an "0x" hex
558+ // prefix, or a "." float suffix all mean "not a decimal immediate".
559+ if start == j || (j < len (s ) && (s [j ] == 'x' || s [j ] == 'X' || s [j ] == '.' )) {
560+ b .WriteByte ('#' )
561+ i ++
562+ continue
563+ }
564+ v , err := strconv .ParseUint (s [start :j ], 10 , 64 )
565+ if err != nil {
566+ b .WriteByte ('#' )
567+ i ++
568+ continue
569+ }
570+ b .WriteByte ('#' )
571+ if neg {
572+ b .WriteByte ('-' )
573+ }
574+ appendHexTo (& b , v )
575+ i = j
576+ }
577+ return b .String ()
578+ }
579+
580+ // appendHexTo writes "0x" + v in lowercase hex to b without fmt's interface
581+ // boxing — these decoders run on every instruction, so the dump/TUI decode paths
582+ // are allocation-sensitive. The scratch array stays on the stack.
583+ func appendHexTo (b * strings.Builder , v uint64 ) {
584+ b .WriteString ("0x" )
585+ var tmp [16 ]byte
586+ b .Write (strconv .AppendUint (tmp [:0 ], v , 16 ))
587+ }
588+
509589// isHexDigit reports whether c is an ASCII hex digit.
510590func isHexDigit (c byte ) bool {
511591 return (c >= '0' && c <= '9' ) || (c >= 'a' && c <= 'f' ) || (c >= 'A' && c <= 'F' )
0 commit comments