Repository: go-delve/delve (go)

Issue:
arm64 bt get endless main.main after nil pointer dereference
Please answer the following before submitting your issue:

Note: Please include any substantial examples (debug session output,
stacktraces, etc) as linked gists.

If this is about source listing not showing up (or breakpoints not being
accepted) while running in a container please read our
[FAQ](https://github.com/go-delve/delve/blob/master/Documentation/faq.md#substpath)
first.

1. What version of Delve are you using (`dlv version`)?
```
Delve Debugger
Version: 1.21.1
Build: $Id: a358c02f24aa7047ecc562b0587dc2d08330b2cf $
```

3. What version of Go are you using? (`go version`)?
`go version go1.20.10 linux/arm64`

```
[root@localhost demo]# go env
GO111MODULE="off"
GOARCH="arm64"
```

5. What operating system and processor architecture are you using?
`CentOS Linux release 7.9.2009 (AltArch)`

7. What did you do?
```
package main

func main() {
	var p_value *int = nil
	*p_value = 1
}
```

`go build demo.go`

```
[root@localhost demo]# dlv exec demo
Type 'help' for list of commands.
(dlv) break main.main
Breakpoint 1 set at 0x65230 for main.main() ./demo.go:5
(dlv) c
> main.main() ./demo.go:5 (hits goroutine(1):1 total:1) (PC: 0x65230)
Warning: debugging optimized function
     1: package main
     2:
     3: func main() {
     4:         var p_value *int = nil
=>   5:         *p_value = 1
     6: }
(dlv) bt
0  0x0000000000065230 in main.main
   at ./demo.go:5
1  0x000000000003d9a0 in runtime.main
   at /home/wsl/go/go1.20.10/src/runtime/proc.go:250
2  0x0000000000062e34 in runtime.goexit
   at /home/wsl/go/go1.20.10/src/runtime/asm_arm64.s:1172
(dlv) n
> [unrecovered-panic] runtime.fatalpanic() /home/wsl/go/go1.20.10/src/runtime/panic.go:1145 (hits goroutine(1):1 total:1) (PC: 0x3b680)
Warning: debugging optimized function
        runtime.curg._panic.arg: interface {}(string) "runtime error: invalid memory address or nil pointer dereference"
  1140: // fatalpanic implements an unrecoverable panic. It is like fatalthrow, except
  1141: // that if msgs != nil, fatalpanic also prints panic messages and decrements
  1142: // runningPanicDefers once main is blocked from exiting.
  1143: //
  1144: //go:nosplit
=>1145: func fatalpanic(msgs *_panic) {
  1146:         pc := getcallerpc()
  1147:         sp := getcallersp()
  1148:         gp := getg()
  1149:         var docrash bool
  1150:         // Switch to the system stack to avoid any stack growth, which
(dlv) bt
 0  0x000000000003b680 in runtime.fatalpanic
    at /home/wsl/go/go1.20.10/src/runtime/panic.go:1145
 1  0x000000000003afac in runtime.gopanic
    at /home/wsl/go/go1.20.10/src/runtime/panic.go:987
 2  0x0000000000039a78 in runtime.panicmem
    at /home/wsl/go/go1.20.10/src/runtime/panic.go:260
 3  0x000000000004fea4 in runtime.sigpanic
    at /home/wsl/go/go1.20.10/src/runtime/signal_unix.go:841
 4  0x0000000000065238 in main.main
    at ./demo.go:5
 5  0x0000000000065238 in 
    at ./demo.go:5
 6  0x0000000000065238 in main.main
    at ./demo.go:5
 7  0x0000000000065238 in main.main
    at ./demo.go:5
 8  0x0000000000065238 in main.main
    at ./demo.go:5
 9  0x0000000000065238 in main.main
    at ./demo.go:5
10  0x0000000000065238 in main.main
    at ./demo.go:5
11  0x0000000000065238 in main.main
    at ./demo.go:5
12  0x0000000000065238 in main.main
    at ./demo.go:5
13  0x0000000000065238 in main.main
    at ./demo.go:5
14  0x0000000000065238 in main.main
    at ./demo.go:5
15  0x0000000000065238 in main.main
    at ./demo.go:5
16  0x0000000000065238 in main.main
    at ./demo.go:5
17  0x0000000000065238 in main.main
    at ./demo.go:5
18  0x0000000000065238 in main.main
    at ./demo.go:5
19  0x0000000000065238 in main.main
    at ./demo.go:5
```
9. What did you expect to see?
After the nil pointer dereference, the backstrace is right.

11. What did you see instead?
After the nil pointer dereference, the bt is wrong with **endless main.main.**

Pull request:
pkg/proc: improve support unwinding from sigpanic
See: https://github.com/golang/go/issues/63862

Fixes #3545

Patch:
diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts
index 9d12b453..02d968db 100644
--- a/.teamcity/settings.kts
+++ b/.teamcity/settings.kts
@@ -40,7 +40,7 @@ val targets = arrayOf(
         "linux/amd64/1.21",
         "linux/amd64/tip",
 
-        "linux/386/1.20",
+        "linux/386/1.21",
 
         "linux/arm64/1.21",
         "linux/arm64/tip",
diff --git a/_fixtures/nilptr.go b/_fixtures/nilptr.go
new file mode 100644
index 00000000..b89a4ae5
--- /dev/null
+++ b/_fixtures/nilptr.go
@@ -0,0 +1,6 @@
+package main
+
+func main() {
+	var p_value *int = nil
+	*p_value = 1
+}
diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go
index 26a16fa0..4f193f1e 100644
--- a/pkg/proc/amd64_arch.go
+++ b/pkg/proc/amd64_arch.go
@@ -112,7 +112,7 @@ func amd64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 		fctxt.CFA = rule
 	}
 
-	// We assume that RBP is the frame pointer and we want to keep it updated,
+	// We assume that RBP is the frame pointer, and we want to keep it updated,
 	// so that we can use it to unwind the stack even when we encounter frames
 	// without descriptor entries.
 	// If there isn't a rule already we emit one.
diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go
index 2e646be4..8cb70459 100644
--- a/pkg/proc/arm64_arch.go
+++ b/pkg/proc/arm64_arch.go
@@ -117,7 +117,7 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary
 		fctxt.CFA = rule
 	}
 
-	// We assume that RBP is the frame pointer and we want to keep it updated,
+	// We assume that RBP is the frame pointer, and we want to keep it updated,
 	// so that we can use it to unwind the stack even when we encounter frames
 	// without descriptor entries.
 	// If there isn't a rule already we emit one.
diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go
index fc3feb00..d774300b 100644
--- a/pkg/proc/stack.go
+++ b/pkg/proc/stack.go
@@ -1,7 +1,9 @@
 package proc
 
 import (
+	"bytes"
 	"debug/dwarf"
+	"encoding/binary"
 	"errors"
 	"fmt"
 	"go/constant"
@@ -481,7 +483,6 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
 	for i, regRule := range framectx.Regs {
 		if logflags.Stack() {
 			logger.Debugf("\t%s rule %s ", it.bi.Arch.RegnumToString(i), ruleString(&regRule, it.bi.Arch.RegnumToString))
-
 		}
 		reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
 		if reg != nil {
@@ -499,6 +500,22 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
 				it.err = err
 			} else {
 				ret = reg.Uint64Val
+				// On systems which use a link register to store the return address of a function,
+				// certain leaf functions may not have correct DWARF information present in the
+				// .debug_frame FDE when unwinding after a fatal signal. This is due to the fact
+				// that runtime.sigpanic inserts a frame to make it look like the function which
+				// triggered the signal called runtime.sigpanic directly, making the value of the
+				// link register unreliable. Instead, treat it as a non-leaf function and read the
+				// return address from the stack. For more details, see:
+				// https://github.com/golang/go/issues/63862#issuecomment-1802672629.
+				if it.frame.Call.Fn != nil && it.frame.Call.Fn.Name == "runtime.sigpanic" && it.bi.Arch.usesLR {
+					buf := make([]byte, 8)
+					_, err := it.mem.ReadMemory(buf, uint64(it.regs.CFA))
+					if err != nil {
+						it.err = err
+					}
+					binary.Read(bytes.NewReader(buf), binary.LittleEndian, &ret)
+				}
 			}
 			retaddr = uint64(it.regs.CFA + regRule.Offset)
 		}
diff --git a/service/api/prettyprint.go b/service/api/prettyprint.go
index a5159e3d..123dab34 100644
--- a/service/api/prettyprint.go
+++ b/service/api/prettyprint.go
@@ -24,11 +24,13 @@ const (
 	prettyTop prettyFlags = 1 << iota
 	prettyNewlines
 	prettyIncludeType
+	prettyShortenType
 )
 
 func (flags prettyFlags) top() bool         { return flags&prettyTop != 0 }
 func (flags prettyFlags) includeType() bool { return flags&prettyIncludeType != 0 }
 func (flags prettyFlags) newlines() bool    { return flags&prettyNewlines != 0 }
+func (flags prettyFlags) shortenType() bool { return flags&prettyShortenType != 0 }
 
 func (flags prettyFlags) set(flag prettyFlags, v bool) prettyFlags {
 	if v {
@@ -45,6 +47,13 @@ func (v *Variable) SinglelineString() string {
 	return buf.String()
 }
 
+// SinglelineStringWithShortTypes returns a representation of v on a single line, with types shortened.
+func (v *Variable) SinglelineStringWithShortTypes() string {
+	var buf bytes.Buffer
+	v.writeTo(&buf, prettyTop|prettyIncludeType|prettyShortenType, "", "")
+	return buf.String()
+}
+
 // SinglelineStringFormatted returns a representation of v on a single line, using the format specified by fmtstr.
 func (v *Variable) SinglelineStringFormatted(fmtstr string) string {
 	var buf bytes.Buffer
@@ -59,6 +68,14 @@ func (v *Variable) MultilineString(indent, fmtstr string) string {
 	return buf.String()
 }
 
+func (v *Variable) typeStr(flags prettyFlags) string {
+	if flags.shortenType() {
+		return ShortenType(v.Type)
+	}
+
+	return v.Type
+}
+
 func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 	if v.Unreadable != "" {
 		fmt.Fprintf(buf, "(unreadable %s)", v.Unreadable)
@@ -67,7 +84,7 @@ func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr stri
 
 	if !flags.top() && v.Addr == 0 && v.Value == "" {
 		if flags.includeType() && v.Type != "void" {
-			fmt.Fprintf(buf, "%s nil", v.Type)
+			fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
 		} else {
 			fmt.Fprint(buf, "nil")
 		}
@@ -83,10 +100,10 @@ func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr stri
 		if v.Type == "" || len(v.Children) == 0 {
 			fmt.Fprint(buf, "nil")
 		} else if v.Children[0].OnlyAddr && v.Children[0].Addr != 0 {
-			v.writePointerTo(buf)
+			v.writePointerTo(buf, flags)
 		} else {
 			if flags.top() && flags.newlines() && v.Children[0].Addr != 0 {
-				v.writePointerTo(buf)
+				v.writePointerTo(buf, flags)
 				fmt.Fprint(buf, "\n")
 			}
 			fmt.Fprint(buf, "*")
@@ -103,14 +120,14 @@ func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr stri
 			v.writeStructTo(buf, flags, indent, fmtstr)
 		} else {
 			if len(v.Children) == 0 {
-				fmt.Fprintf(buf, "%s nil", v.Type)
+				fmt.Fprintf(buf, "%s nil", v.typeStr(flags))
 			} else {
-				fmt.Fprintf(buf, "%s %s/%s", v.Type, v.Children[0].Value, v.Children[1].Value)
+				fmt.Fprintf(buf, "%s %s/%s", v.typeStr(flags), v.Children[0].Value, v.Children[1].Value)
 			}
 		}
 	case reflect.Struct:
 		if v.Value != "" {
-			fmt.Fprintf(buf, "%s(%s)", v.Type, v.Value)
+			fmt.Fprintf(buf, "%s(%s)", v.typeStr(flags), v.Value)
 			flags = flags.set(prettyIncludeType, false)
 		}
 		v.writeStructTo(buf, flags, indent, fmtstr)
@@ -123,13 +140,13 @@ func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr stri
 		}
 		if flags.includeType() {
 			if v.Children[0].Kind == reflect.Invalid {
-				fmt.Fprintf(buf, "%s ", v.Type)
+				fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 				if v.Children[0].Addr == 0 {
 					fmt.Fprint(buf, "nil")
 					return
 				}
 			} else {
-				fmt.Fprintf(buf, "%s(%s) ", v.Type, v.Children[0].Type)
+				fmt.Fprintf(buf, "%s(%s) ", v.typeStr(flags), v.Children[0].Type)
 			}
 		}
 		data := v.Children[0]
@@ -145,9 +162,9 @@ func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr stri
 			}
 		} else if data.OnlyAddr {
 			if strings.Contains(v.Type, "/") {
-				fmt.Fprintf(buf, "*(*%q)(%#x)", v.Type, v.Addr)
+				fmt.Fprintf(buf, "*(*%q)(%#x)", v.typeStr(flags), v.Addr)
 			} else {
-				fmt.Fprintf(buf, "*(*%s)(%#x)", v.Type, v.Addr)
+				fmt.Fprintf(buf, "*(*%s)(%#x)", v.typeStr(flags), v.Addr)
 			}
 		} else {
 			v.Children[0].writeTo(buf, flags.set(prettyTop, false).set(prettyIncludeType, !flags.includeType()), indent, fmtstr)
@@ -165,11 +182,11 @@ func (v *Variable) writeTo(buf io.Writer, flags prettyFlags, indent, fmtstr stri
 	}
 }
 
-func (v *Variable) writePointerTo(buf io.Writer) {
+func (v *Variable) writePointerTo(buf io.Writer, flags prettyFlags) {
 	if strings.Contains(v.Type, "/") {
-		fmt.Fprintf(buf, "(%q)(%#x)", v.Type, v.Children[0].Addr)
+		fmt.Fprintf(buf, "(%q)(%#x)", v.typeStr(flags), v.Children[0].Addr)
 	} else {
-		fmt.Fprintf(buf, "(%s)(%#x)", v.Type, v.Children[0].Addr)
+		fmt.Fprintf(buf, "(%s)(%#x)", v.typeStr(flags), v.Children[0].Addr)
 	}
 }
 
@@ -248,7 +265,7 @@ func ExtractIntValue(s string) string {
 
 func (v *Variable) writeSliceTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 	if flags.includeType() {
-		fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.Type, v.Len, v.Cap)
+		fmt.Fprintf(buf, "%s len: %d, cap: %d, ", v.typeStr(flags), v.Len, v.Cap)
 	}
 	if v.Base == 0 && len(v.Children) == 0 {
 		fmt.Fprintf(buf, "nil")
@@ -259,7 +276,7 @@ func (v *Variable) writeSliceTo(buf io.Writer, flags prettyFlags, indent, fmtstr
 
 func (v *Variable) writeArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 	if flags.includeType() {
-		fmt.Fprintf(buf, "%s ", v.Type)
+		fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 	}
 	v.writeSliceOrArrayTo(buf, flags, indent, fmtstr)
 }
@@ -267,15 +284,15 @@ func (v *Variable) writeArrayTo(buf io.Writer, flags prettyFlags, indent, fmtstr
 func (v *Variable) writeStructTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 	if int(v.Len) != len(v.Children) && len(v.Children) == 0 {
 		if strings.Contains(v.Type, "/") {
-			fmt.Fprintf(buf, "(*%q)(%#x)", v.Type, v.Addr)
+			fmt.Fprintf(buf, "(*%q)(%#x)", v.typeStr(flags), v.Addr)
 		} else {
-			fmt.Fprintf(buf, "(*%s)(%#x)", v.Type, v.Addr)
+			fmt.Fprintf(buf, "(*%s)(%#x)", v.typeStr(flags), v.Addr)
 		}
 		return
 	}
 
 	if flags.includeType() {
-		fmt.Fprintf(buf, "%s ", v.Type)
+		fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 	}
 
 	nl := v.shouldNewlineStruct(flags.newlines())
@@ -310,7 +327,7 @@ func (v *Variable) writeStructTo(buf io.Writer, flags prettyFlags, indent, fmtst
 
 func (v *Variable) writeMapTo(buf io.Writer, flags prettyFlags, indent, fmtstr string) {
 	if flags.includeType() {
-		fmt.Fprintf(buf, "%s ", v.Type)
+		fmt.Fprintf(buf, "%s ", v.typeStr(flags))
 	}
 	if v.Base == 0 && len(v.Children) == 0 {
 		fmt.Fprintf(buf, "nil")
diff --git a/service/api/shorten_type.go b/service/api/shorten_type.go
new file mode 100644
index 00000000..0e3ca8e3
--- /dev/null
+++ b/service/api/shorten_type.go
@@ -0,0 +1,102 @@
+package api
+
+import (
+	"strings"
+	"unicode"
+)
+
+func ShortenType(typ string) string {
+	out, ok := shortenTypeEx(typ)
+	if !ok {
+		return typ
+	}
+	return out
+}
+
+func shortenTypeEx(typ string) (string, bool) {
+	switch {
+	case strings.HasPrefix(typ, "["):
+		for i := range typ {
+			if typ[i] == ']' {
+				sub, ok := shortenTypeEx(typ[i+1:])
+				return typ[:i+1] + sub, ok
+			}
+		}
+		return "", false
+	case strings.HasPrefix(typ, "*"):
+		sub, ok := shortenTypeEx(typ[1:])
+		return "*" + sub, ok
+	case strings.HasPrefix(typ, "map["):
+		depth := 1
+		for i := 4; i < len(typ); i++ {
+			switch typ[i] {
+			case '[':
+				depth++
+			case ']':
+				depth--
+				if depth == 0 {
+					key, keyok := shortenTypeEx(typ[4:i])
+					val, valok := shortenTypeEx(typ[i+1:])
+					return "map[" + key + "]" + val, keyok && valok
+				}
+			}
+		}
+		return "", false
+	case typ == "interface {}" || typ == "interface{}":
+		return typ, true
+	case typ == "struct {}" || typ == "struct{}":
+		return typ, true
+	default:
+		if containsAnonymousType(typ) {
+			return "", false
+		}
+
+		if lbrk := strings.Index(typ, "["); lbrk >= 0 {
+			if typ[len(typ)-1] != ']' {
+				return "", false
+			}
+			typ0, ok := shortenTypeEx(typ[:lbrk])
+			if !ok {
+				return "", false
+			}
+			args := strings.Split(typ[lbrk+1:len(typ)-1], ",")
+			for i := range args {
+				var ok bool
+				args[i], ok = shortenTypeEx(strings.TrimSpace(args[i]))
+				if !ok {
+					return "", false
+				}
+			}
+			return typ0 + "[" + strings.Join(args, ", ") + "]", true
+		}
+
+		slashnum := 0
+		slash := -1
+		for i, ch := range typ {
+			if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' && ch != '.' && ch != '/' && ch != '@' && ch != '%' && ch != '-' {
+				return "", false
+			}
+			if ch == '/' {
+				slash = i
+				slashnum++
+			}
+		}
+		if slashnum <= 1 || slash < 0 {
+			return typ, true
+		}
+		return typ[slash+1:], true
+	}
+}
+
+func containsAnonymousType(typ string) bool {
+	for _, thing := range []string{"interface {", "interface{", "struct {", "struct{", "func (", "func("} {
+		idx := strings.Index(typ, thing)
+		if idx >= 0 && idx+len(thing) < len(typ) {
+			ch := typ[idx+len(thing)]
+			if ch != '}' && ch != ')' {
+				return true
+			}
+		}
+	}
+	return false
+}
diff --git a/service/dap/server.go b/service/dap/server.go
index 21e47022..1880fd2e 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -2589,7 +2589,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr
 		}
 		return s.variableHandles.create(&fullyQualifiedVariable{v, qualifiedNameOrExpr, false /*not a scope*/, 0})
 	}
-	value = api.ConvertVar(v).SinglelineString()
+	value = api.ConvertVar(v).SinglelineStringWithShortTypes()
 	if v.Unreadable != nil {
 		return value, 0
 	}
@@ -2603,7 +2603,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr
 		// TODO(polina): Get *proc.Variable object from debugger instead. Export a function to set v.loaded to false
 		// and call v.loadValue gain with a different load config. It's more efficient, and it's guaranteed to keep
 		// working with generics.
-		value = api.ConvertVar(v).SinglelineString()
+		value = api.ConvertVar(v).SinglelineStringWithShortTypes()
 		typeName := api.PrettyTypeName(v.DwarfType)
 		loadExpr := fmt.Sprintf("*(*%q)(%#x)", typeName, v.Addr)
 		s.config.log.Debugf("loading %s (type %s) with %s", qualifiedNameOrExpr, typeName, loadExpr)
@@ -2617,7 +2617,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr
 		} else {
 			v.Children = vLoaded.Children
 			v.Value = vLoaded.Value
-			value = api.ConvertVar(v).SinglelineString()
+			value = api.ConvertVar(v).SinglelineStringWithShortTypes()
 		}
 		return value
 	}
@@ -2649,7 +2649,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr
 					} else {
 						cLoaded.Name = v.Children[0].Name // otherwise, this will be the pointer expression
 						v.Children = []proc.Variable{*cLoaded}
-						value = api.ConvertVar(v).SinglelineString()
+						value = api.ConvertVar(v).SinglelineStringWithShortTypes()
 					}
 				} else {
 					value = reloadVariable(v, qualifiedNameOrExpr)


Test patch:
diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md
index 3d87fac4..15562b46 100644
--- a/Documentation/backend_test_health.md
+++ b/Documentation/backend_test_health.md
@@ -1,6 +1,7 @@
 Tests skipped by each supported backend:
 
-* 386 skipped = 6
+* 386 skipped = 7
+	* 1 broken
 	* 3 broken - cgo stacktraces
 	* 3 not implemented
 * arm64 skipped = 1
diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go
index 0846ab71..3345603b 100644
--- a/pkg/proc/proc_test.go
+++ b/pkg/proc/proc_test.go
@@ -4683,8 +4683,8 @@ func TestCgoStacktrace2(t *testing.T) {
 	if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 		skipOn(t, "upstream issue", "windows")
 		skipOn(t, "broken", "arm64")
-		skipOn(t, "broken", "386")
 	}
+	skipOn(t, "broken", "386")
 	skipOn(t, "broken - cgo stacktraces", "darwin", "arm64")
 	skipOn(t, "broken", "ppc64le")
 	protest.MustHaveCgo(t)
@@ -6358,3 +6358,27 @@ func TestNextGenericMethodThroughInterface(t *testing.T) {
 		})
 	}
 }
+
+func TestIssue3545(t *testing.T) {
+	protest.AllowRecording(t)
+	withTestProcessArgs("nilptr", t, "", []string{}, protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
+		err := grp.Continue()
+		if err != nil && err.Error() == "bad access" {
+			grp.Continue()
+		}
+		locations, err := proc.ThreadStacktrace(p, p.CurrentThread(), 40)
+		assertNoError(err, t, "Stacktrace()")
+		var foundMain bool
+		for _, loc := range locations {
+			if loc.Call.Fn != nil && loc.Call.Fn.Name == "main.main" {
+				if foundMain {
+					t.Fatal("main.main found more than once in the stacktrace")
+				}
+				foundMain = true
+			}
+		}
+		if !foundMain {
+			t.Fatal("did not find main.main in stack trace")
+		}
+	})
+}
diff --git a/service/api/shorten_type_test.go b/service/api/shorten_type_test.go
new file mode 100644
index 00000000..8a2595a8
--- /dev/null
+++ b/service/api/shorten_type_test.go
@@ -0,0 +1,36 @@
+package api
+
+import "testing"
+
+func TestShortenType(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+	}{
+		{"long/package/path/pkg.A", "pkg.A"},
+		{"[]long/package/path/pkg.A", "[]pkg.A"},
+		{"map[long/package/path/pkg.A]long/package/path/pkg.B", "map[pkg.A]pkg.B"},
+		{"map[long/package/path/pkg.A]interface {}", "map[pkg.A]interface {}"},
+		{"map[long/package/path/pkg.A]interface{}", "map[pkg.A]interface{}"},
+		{"map[long/package/path/pkg.A]struct {}", "map[pkg.A]struct {}"},
+		{"map[long/package/path/pkg.A]struct{}", "map[pkg.A]struct{}"},
+		{"map[long/package/path/pkg.A]map[long/package/path/pkg.B]long/package/path/pkg.C", "map[pkg.A]map[pkg.B]pkg.C"},
+		{"map[long/package/path/pkg.A][]long/package/path/pkg.B", "map[pkg.A][]pkg.B"},
+		{"map[uint64]*github.com/aarzilli/dwarf5/dwarf.typeUnit", "map[uint64]*dwarf.typeUnit"},
+		{"uint8", "uint8"},
+		{"encoding/binary", "encoding/binary"},
+		{"*github.com/go-delve/delve/pkg/proc.Target", "*proc.Target"},
+		{"long/package/path/pkg.Parametric[long/package/path/pkg.A, map[long/package/path/pkg.B]long/package/path/pkg.A]", "pkg.Parametric[pkg.A, map[pkg.B]pkg.A]"},
+		{"[]long/package/path/pkg.Parametric[long/package/path/pkg.A]", "[]pkg.Parametric[pkg.A]"},
+		{"[24]long/package/path/pkg.A", "[24]pkg.A"},
+		{"chan func()", "chan func()"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.input, func(t *testing.T) {
+			result := ShortenType(tt.input)
+			if result != tt.expected {
+				t.Errorf("shortenTypeEx() got = %v, want %v", result, tt.expected)
+			}
+		})
+	}
+}
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 611ddde2..9e016605 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -1282,7 +1282,7 @@ func TestFunctionNameFormattingInStackTrace(t *testing.T) {
 			[]onBreakpoint{{
 				// Stop at line 36
 				execute: func() {
-					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
+					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 						client.StepInRequest(1)
 						client.ExpectStepInResponse(t)
 						client.ExpectStoppedEvent(t)
@@ -2463,7 +2463,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
 			[]onBreakpoint{{
 				// Stop at line 36
 				execute: func() {
-					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
+					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 						client.StepInRequest(1)
 						client.ExpectStepInResponse(t)
 						client.ExpectStoppedEvent(t)
@@ -2502,7 +2502,7 @@ func TestGlobalScopeAndVariables(t *testing.T) {
 					client.VariablesRequest(globalsScope)
 					globals := client.ExpectVariablesResponse(t)
 					checkChildren(t, globals, "Globals", 1)
-					ref := checkVarExact(t, globals, 0, "SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType {X: 0}", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType", hasChildren)
+					ref := checkVarExact(t, globals, 0, "SomeVar", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeVar", "pkg.SomeType {X: 0}", "github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType", hasChildren)
 
 					if ref > 0 {
 						client.VariablesRequest(ref)
@@ -2538,7 +2538,7 @@ func TestRegistersScopeAndVariables(t *testing.T) {
 			[]onBreakpoint{{
 				// Stop at line 36
 				execute: func() {
-					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) {
+					if runtime.GOARCH == "386" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 21) {
 						client.StepInRequest(1)
 						client.ExpectStepInResponse(t)
 						client.ExpectStoppedEvent(t)