Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
pubx
delve
提交
07473f04
D
delve
项目概览
pubx
/
delve
通知
0
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
delve
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
07473f04
编写于
6月 17, 2015
作者:
A
aarzilli
提交者:
Derek Parker
6月 20, 2015
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Implement stack command
Finishes #63 #64
上级
cc5e5c78
变更
14
隐藏空白更改
内联
并排
Showing
14 changed file
with
371 addition
and
40 deletion
+371
-40
_fixtures/goroutinestackprog.go
_fixtures/goroutinestackprog.go
+25
-0
_fixtures/stacktraceprog.go
_fixtures/stacktraceprog.go
+18
-0
proc/proc.go
proc/proc.go
+16
-2
proc/proc_test.go
proc/proc_test.go
+110
-0
proc/stack.go
proc/stack.go
+38
-15
proc/threads.go
proc/threads.go
+1
-0
proc/variables.go
proc/variables.go
+4
-1
service/api/conversions.go
service/api/conversions.go
+28
-20
service/api/types.go
service/api/types.go
+7
-0
service/client.go
service/client.go
+3
-0
service/debugger/debugger.go
service/debugger/debugger.go
+46
-0
service/rest/client.go
service/rest/client.go
+9
-0
service/rest/server.go
service/rest/server.go
+24
-0
terminal/command.go
terminal/command.go
+42
-2
未找到文件。
_fixtures/goroutinestackprog.go
0 → 100644
浏览文件 @
07473f04
package
main
import
"runtime"
const
N
=
10
func
agoroutine
(
done
chan
<-
struct
{})
{
done
<-
struct
{}{}
}
func
stacktraceme
()
{
return
}
func
main
()
{
done
:=
make
(
chan
struct
{})
for
i
:=
0
;
i
<
N
;
i
++
{
go
agoroutine
(
done
)
}
runtime
.
Gosched
()
stacktraceme
()
for
i
:=
0
;
i
<
N
;
i
++
{
<-
done
}
}
_fixtures/stacktraceprog.go
0 → 100644
浏览文件 @
07473f04
package
main
func
stacktraceme
()
{
return
}
func
func1
()
{
stacktraceme
()
}
func
func2
(
f
func
())
{
f
()
}
func
main
()
{
func1
()
func2
(
func1
)
}
proc/proc.go
浏览文件 @
07473f04
...
...
@@ -424,10 +424,21 @@ func (dbp *DebuggedProcess) SwitchThread(tid int) error {
// Delve cares about from the internal runtime G structure.
func
(
dbp
*
DebuggedProcess
)
GoroutinesInfo
()
([]
*
G
,
error
)
{
var
(
allg
[]
*
G
rdr
=
dbp
.
DwarfReader
()
threadg
=
map
[
int
]
*
Thread
{}
allg
[]
*
G
rdr
=
dbp
.
DwarfReader
()
)
for
i
:=
range
dbp
.
Threads
{
if
dbp
.
Threads
[
i
]
.
blocked
()
{
continue
}
g
,
_
:=
dbp
.
Threads
[
i
]
.
getG
()
if
g
!=
nil
{
threadg
[
g
.
Id
]
=
dbp
.
Threads
[
i
]
}
}
addr
,
err
:=
rdr
.
AddrFor
(
"runtime.allglen"
)
if
err
!=
nil
{
return
nil
,
err
...
...
@@ -451,6 +462,9 @@ func (dbp *DebuggedProcess) GoroutinesInfo() ([]*G, error) {
if
err
!=
nil
{
return
nil
,
err
}
if
thread
,
allocated
:=
threadg
[
g
.
Id
];
allocated
{
g
.
thread
=
thread
}
allg
=
append
(
allg
,
g
)
}
return
allg
,
nil
...
...
proc/proc_test.go
浏览文件 @
07473f04
...
...
@@ -409,3 +409,113 @@ func TestSwitchThread(t *testing.T) {
}
})
}
type
loc
struct
{
line
int
fn
string
}
func
(
l1
*
loc
)
match
(
l2
Location
)
bool
{
if
l1
.
line
>=
0
{
if
l1
.
line
!=
l2
.
Line
-
1
{
return
false
}
}
return
l1
.
fn
==
l2
.
Fn
.
Name
}
func
TestStacktrace
(
t
*
testing
.
T
)
{
stacks
:=
[][]
loc
{
[]
loc
{{
8
,
"main.func1"
},
{
16
,
"main.main"
}},
[]
loc
{{
8
,
"main.func1"
},
{
12
,
"main.func2"
},
{
17
,
"main.main"
}},
}
withTestProcess
(
"stacktraceprog"
,
t
,
func
(
p
*
DebuggedProcess
,
fixture
protest
.
Fixture
)
{
bp
,
err
:=
p
.
BreakByLocation
(
"main.stacktraceme"
)
assertNoError
(
err
,
t
,
"BreakByLocation()"
)
for
i
:=
range
stacks
{
assertNoError
(
p
.
Continue
(),
t
,
"Continue()"
)
locations
,
err
:=
p
.
CurrentThread
.
Stacktrace
(
40
)
assertNoError
(
err
,
t
,
"Stacktrace()"
)
if
len
(
locations
)
!=
len
(
stacks
[
i
])
+
2
{
t
.
Fatalf
(
"Wrong stack trace size %d %d
\n
"
,
len
(
locations
),
len
(
stacks
[
i
])
+
2
)
}
for
j
:=
range
stacks
[
i
]
{
if
!
stacks
[
i
][
j
]
.
match
(
locations
[
j
])
{
t
.
Fatalf
(
"Wrong stack trace pos %d
\n
"
,
j
)
}
}
}
p
.
Clear
(
bp
.
Addr
)
p
.
Continue
()
})
}
func
stackMatch
(
stack
[]
loc
,
locations
[]
Location
)
bool
{
if
len
(
stack
)
>
len
(
locations
)
{
return
false
}
for
i
:=
range
stack
{
if
!
stack
[
i
]
.
match
(
locations
[
i
])
{
return
false
}
}
return
true
}
func
TestStacktraceGoroutine
(
t
*
testing
.
T
)
{
mainStack
:=
[]
loc
{{
21
,
"main.main"
}}
agoroutineStack
:=
[]
loc
{{
-
1
,
"runtime.goparkunlock"
},
{
-
1
,
"runtime.chansend"
},
{
-
1
,
"runtime.chansend1"
},
{
8
,
"main.agoroutine"
}}
withTestProcess
(
"goroutinestackprog"
,
t
,
func
(
p
*
DebuggedProcess
,
fixture
protest
.
Fixture
)
{
bp
,
err
:=
p
.
BreakByLocation
(
"main.stacktraceme"
)
assertNoError
(
err
,
t
,
"BreakByLocation()"
)
assertNoError
(
p
.
Continue
(),
t
,
"Continue()"
)
gs
,
err
:=
p
.
GoroutinesInfo
()
assertNoError
(
err
,
t
,
"GoroutinesInfo"
)
agoroutineCount
:=
0
mainCount
:=
0
for
_
,
g
:=
range
gs
{
locations
,
_
:=
p
.
GoroutineStacktrace
(
g
,
40
)
assertNoError
(
err
,
t
,
"GoroutineStacktrace()"
)
if
stackMatch
(
mainStack
,
locations
)
{
mainCount
++
}
if
stackMatch
(
agoroutineStack
,
locations
)
{
agoroutineCount
++
}
else
{
t
.
Logf
(
"Non-goroutine stack: (%d)"
,
len
(
locations
))
for
i
:=
range
locations
{
name
:=
""
if
locations
[
i
]
.
Fn
!=
nil
{
name
=
locations
[
i
]
.
Fn
.
Name
}
t
.
Logf
(
"
\t
%s:%d %s
\n
"
,
locations
[
i
]
.
File
,
locations
[
i
]
.
Line
,
name
)
}
}
}
if
mainCount
!=
1
{
t
.
Fatalf
(
"Main goroutine stack not found"
)
}
if
agoroutineCount
!=
10
{
t
.
Fatalf
(
"Goroutine stacks not found (%d)"
,
agoroutineCount
)
}
p
.
Clear
(
bp
.
Addr
)
p
.
Continue
()
})
}
proc/stack.go
浏览文件 @
07473f04
package
proc
import
(
"debug/gosym"
"encoding/binary"
)
type
stackLocation
struct
{
addr
uint64
file
string
line
int
fn
*
gosym
.
Func
}
// Takes an offset from RSP and returns the address of the
// instruction the currect function is going to return to.
func
(
thread
*
Thread
)
ReturnAddress
()
(
uint64
,
error
)
{
regs
,
err
:=
thread
.
Registers
(
)
locations
,
err
:=
thread
.
Stacktrace
(
1
)
if
err
!=
nil
{
return
0
,
err
}
locations
,
err
:=
thread
.
dbp
.
stacktrace
(
regs
.
PC
(),
regs
.
SP
(),
1
)
return
locations
[
0
]
.
PC
,
nil
}
// Returns the stack trace for thread
// Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses
func
(
thread
*
Thread
)
Stacktrace
(
depth
int
)
([]
Location
,
error
)
{
regs
,
err
:=
thread
.
Registers
()
if
err
!=
nil
{
return
0
,
err
return
nil
,
err
}
locations
,
err
:=
thread
.
dbp
.
stacktrace
(
regs
.
PC
(),
regs
.
SP
(),
depth
)
if
err
!=
nil
{
return
nil
,
err
}
return
locations
[
0
]
.
addr
,
nil
return
locations
,
nil
}
// Returns the stack trace for a goroutine
// Note that it doesn't include the current frame and the locations in the array are return addresses not call addresses
func
(
dbp
*
DebuggedProcess
)
GoroutineStacktrace
(
g
*
G
,
depth
int
)
([]
Location
,
error
)
{
if
g
.
thread
!=
nil
{
return
g
.
thread
.
Stacktrace
(
depth
)
}
return
dbp
.
stacktrace
(
g
.
PC
,
g
.
SP
,
depth
)
}
func
(
dbp
*
DebuggedProcess
)
GoroutineLocation
(
g
*
G
)
*
Location
{
f
,
l
,
fn
:=
dbp
.
PCToLine
(
g
.
PC
)
return
&
Location
{
PC
:
g
.
PC
,
File
:
f
,
Line
:
l
,
Fn
:
fn
}
}
type
NullAddrError
struct
{}
...
...
@@ -32,12 +48,12 @@ func (n NullAddrError) Error() string {
return
"NULL address"
}
func
(
dbp
*
DebuggedProcess
)
stacktrace
(
pc
,
sp
uint64
,
depth
int
)
([]
stack
Location
,
error
)
{
func
(
dbp
*
DebuggedProcess
)
stacktrace
(
pc
,
sp
uint64
,
depth
int
)
([]
Location
,
error
)
{
var
(
ret
=
pc
data
=
make
([]
byte
,
dbp
.
arch
.
PtrSize
())
btoffset
int64
locations
[]
stack
Location
locations
[]
Location
retaddr
uintptr
)
for
i
:=
int64
(
0
);
i
<
int64
(
depth
);
i
++
{
...
...
@@ -55,8 +71,15 @@ func (dbp *DebuggedProcess) stacktrace(pc, sp uint64, depth int) ([]stackLocatio
return
nil
,
err
}
ret
=
binary
.
LittleEndian
.
Uint64
(
data
)
if
ret
<=
0
{
break
}
f
,
l
,
fn
:=
dbp
.
goSymTable
.
PCToLine
(
ret
)
locations
=
append
(
locations
,
stackLocation
{
addr
:
ret
,
file
:
f
,
line
:
l
,
fn
:
fn
})
locations
=
append
(
locations
,
Location
{
PC
:
ret
,
File
:
f
,
Line
:
l
,
Fn
:
fn
})
if
fn
!=
nil
&&
fn
.
Name
==
"runtime.goexit"
{
break
}
}
return
locations
,
nil
}
proc/threads.go
浏览文件 @
07473f04
...
...
@@ -308,5 +308,6 @@ func (thread *Thread) getG() (g *G, err error) {
return
nil
,
err
}
g
,
err
=
parseG
(
thread
,
regs
.
CX
(),
false
)
g
.
thread
=
thread
return
}
proc/variables.go
浏览文件 @
07473f04
...
...
@@ -53,6 +53,9 @@ type G struct {
// PC of entry to top-most deferred function.
DeferPC
uint64
// Thread that this goroutine is currently allocated to
thread
*
Thread
}
// Returns whether the goroutine is blocked on
...
...
@@ -68,7 +71,7 @@ func (g *G) chanRecvReturnAddr(dbp *DebuggedProcess) (uint64, error) {
return
0
,
err
}
topLoc
:=
locs
[
len
(
locs
)
-
1
]
return
topLoc
.
addr
,
nil
return
topLoc
.
PC
,
nil
}
// NoGError returned when a G could not be found
...
...
service/api/conversions.go
浏览文件 @
07473f04
package
api
import
"github.com/derekparker/delve/proc"
import
(
"debug/gosym"
"github.com/derekparker/delve/proc"
)
// convertBreakpoint converts an internal breakpoint to an API Breakpoint.
func
ConvertBreakpoint
(
bp
*
proc
.
Breakpoint
)
*
Breakpoint
{
...
...
@@ -27,14 +30,7 @@ func ConvertThread(th *proc.Thread) *Thread {
pc
=
loc
.
PC
file
=
loc
.
File
line
=
loc
.
Line
if
loc
.
Fn
!=
nil
{
function
=
&
Function
{
Name
:
loc
.
Fn
.
Name
,
Type
:
loc
.
Fn
.
Type
,
Value
:
loc
.
Fn
.
Value
,
GoType
:
loc
.
Fn
.
GoType
,
}
}
function
=
ConvertFunction
(
loc
.
Fn
)
}
return
&
Thread
{
...
...
@@ -55,23 +51,35 @@ func ConvertVar(v *proc.Variable) Variable {
}
}
// convertGoroutine converts an internal Goroutine to an API Goroutine.
func
ConvertGoroutine
(
g
*
proc
.
G
)
*
Goroutine
{
var
function
*
Function
if
g
.
Func
!=
nil
{
function
=
&
Function
{
Name
:
g
.
Func
.
Name
,
Type
:
g
.
Func
.
Typ
e
,
Value
:
g
.
Func
.
Valu
e
,
GoType
:
g
.
Func
.
GoTyp
e
,
}
func
ConvertFunction
(
fn
*
gosym
.
Func
)
*
Function
{
if
fn
==
nil
{
return
nil
}
return
&
Function
{
Name
:
fn
.
Nam
e
,
Type
:
fn
.
Typ
e
,
Value
:
fn
.
Valu
e
,
GoType
:
fn
.
GoType
,
}
}
// convertGoroutine converts an internal Goroutine to an API Goroutine.
func
ConvertGoroutine
(
g
*
proc
.
G
)
*
Goroutine
{
return
&
Goroutine
{
ID
:
g
.
Id
,
PC
:
g
.
PC
,
File
:
g
.
File
,
Line
:
g
.
Line
,
Function
:
function
,
Function
:
ConvertFunction
(
g
.
Func
),
}
}
func
ConvertLocation
(
loc
proc
.
Location
)
Location
{
return
Location
{
PC
:
loc
.
PC
,
File
:
loc
.
File
,
Line
:
loc
.
Line
,
Function
:
ConvertFunction
(
loc
.
Fn
),
}
}
service/api/types.go
浏览文件 @
07473f04
...
...
@@ -41,6 +41,13 @@ type Thread struct {
Function
*
Function
`json:"function,omitempty"`
}
type
Location
struct
{
PC
uint64
`json:"pc"`
File
string
`json:"file"`
Line
int
`json:"line"`
Function
*
Function
`json:"function,omitempty"`
}
// Function represents thread-scoped function information.
type
Function
struct
{
// Name is the function name.
...
...
service/client.go
浏览文件 @
07473f04
...
...
@@ -60,4 +60,7 @@ type Client interface {
// ListGoroutines lists all goroutines.
ListGoroutines
()
([]
*
api
.
Goroutine
,
error
)
// Returns stacktrace
Stacktrace
(
goroutineId
,
depth
int
)
([]
*
api
.
Location
,
error
)
}
service/debugger/debugger.go
浏览文件 @
07473f04
...
...
@@ -309,3 +309,49 @@ func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
}
return
goroutines
,
err
}
func
(
d
*
Debugger
)
Stacktrace
(
goroutineId
,
depth
int
)
([]
api
.
Location
,
error
)
{
var
rawlocs
[]
proc
.
Location
var
rawloc
*
proc
.
Location
var
err
error
if
goroutineId
<
0
{
rawlocs
,
err
=
d
.
process
.
CurrentThread
.
Stacktrace
(
depth
)
if
err
!=
nil
{
return
nil
,
err
}
rawloc
,
err
=
d
.
process
.
CurrentThread
.
Location
()
if
err
!=
nil
{
return
nil
,
err
}
}
else
{
gs
,
err
:=
d
.
process
.
GoroutinesInfo
()
if
err
!=
nil
{
return
nil
,
err
}
for
_
,
g
:=
range
gs
{
if
g
.
Id
==
goroutineId
{
rawlocs
,
err
=
d
.
process
.
GoroutineStacktrace
(
g
,
depth
)
if
err
!=
nil
{
return
nil
,
err
}
rawloc
=
d
.
process
.
GoroutineLocation
(
g
)
break
}
}
if
rawlocs
==
nil
{
return
nil
,
fmt
.
Errorf
(
"Unknown goroutine id %d
\n
"
,
goroutineId
)
}
}
locations
:=
make
([]
api
.
Location
,
0
,
len
(
rawlocs
)
+
1
)
locations
=
append
(
locations
,
api
.
ConvertLocation
(
*
rawloc
))
for
i
:=
range
rawlocs
{
rawlocs
[
i
]
.
Line
--
locations
=
append
(
locations
,
api
.
ConvertLocation
(
rawlocs
[
i
]))
}
return
locations
,
nil
}
service/rest/client.go
浏览文件 @
07473f04
...
...
@@ -266,6 +266,15 @@ func (c *RESTClient) ListGoroutines() ([]*api.Goroutine, error) {
return
goroutines
,
nil
}
func
(
c
*
RESTClient
)
Stacktrace
(
goroutineId
,
depth
int
)
([]
*
api
.
Location
,
error
)
{
var
locations
[]
*
api
.
Location
err
:=
c
.
doGET
(
fmt
.
Sprintf
(
"/goroutines/%d/trace?depth=%d"
,
goroutineId
,
depth
),
&
locations
)
if
err
!=
nil
{
return
nil
,
err
}
return
locations
,
nil
}
// TODO: how do we use http.Client with a UNIX socket URI?
func
(
c
*
RESTClient
)
url
(
path
string
)
string
{
return
fmt
.
Sprintf
(
"http://%s%s"
,
c
.
addr
,
path
)
...
...
service/rest/server.go
浏览文件 @
07473f04
...
...
@@ -82,6 +82,7 @@ func (s *RESTServer) Run() error {
Route
(
ws
.
GET
(
"/threads/{thread-id}/vars"
)
.
To
(
s
.
listThreadPackageVars
))
.
Route
(
ws
.
GET
(
"/threads/{thread-id}/eval/{symbol}"
)
.
To
(
s
.
evalThreadSymbol
))
.
Route
(
ws
.
GET
(
"/goroutines"
)
.
To
(
s
.
listGoroutines
))
.
Route
(
ws
.
GET
(
"/goroutines/{goroutine-id}/trace"
)
.
To
(
s
.
stacktraceGoroutine
))
.
Route
(
ws
.
POST
(
"/command"
)
.
To
(
s
.
doCommand
))
.
Route
(
ws
.
GET
(
"/sources"
)
.
To
(
s
.
listSources
))
.
Route
(
ws
.
GET
(
"/functions"
)
.
To
(
s
.
listFunctions
))
.
...
...
@@ -171,6 +172,29 @@ func (s *RESTServer) getBreakpoint(request *restful.Request, response *restful.R
response
.
WriteEntity
(
found
)
}
func
(
s
*
RESTServer
)
stacktraceGoroutine
(
request
*
restful
.
Request
,
response
*
restful
.
Response
)
{
goroutineId
,
err
:=
strconv
.
Atoi
(
request
.
PathParameter
(
"goroutine-id"
))
if
err
!=
nil
{
writeError
(
response
,
http
.
StatusBadRequest
,
"invalid goroutine id"
)
return
}
depth
,
err
:=
strconv
.
Atoi
(
request
.
QueryParameter
(
"depth"
))
if
err
!=
nil
{
writeError
(
response
,
http
.
StatusBadRequest
,
"invalid depth"
)
return
}
locations
,
err
:=
s
.
debugger
.
Stacktrace
(
goroutineId
,
depth
)
if
err
!=
nil
{
writeError
(
response
,
http
.
StatusBadRequest
,
err
.
Error
())
return
}
response
.
WriteHeader
(
http
.
StatusOK
)
response
.
WriteEntity
(
locations
)
}
func
(
s
*
RESTServer
)
listBreakpoints
(
request
*
restful
.
Request
,
response
*
restful
.
Response
)
{
response
.
WriteEntity
(
s
.
debugger
.
Breakpoints
())
}
...
...
terminal/command.go
浏览文件 @
07473f04
...
...
@@ -59,6 +59,7 @@ func DebugCommands(client service.Client) *Commands {
{
aliases
:
[]
string
{
"print"
,
"p"
},
cmdFn
:
printVar
,
helpMsg
:
"Evaluate a variable."
},
{
aliases
:
[]
string
{
"info"
},
cmdFn
:
info
,
helpMsg
:
"Subcommands: args, funcs, locals, sources, vars, or regs."
},
{
aliases
:
[]
string
{
"exit"
},
cmdFn
:
nullCommand
,
helpMsg
:
"Exit the debugger."
},
{
aliases
:
[]
string
{
"stack"
},
cmdFn
:
stackCommand
,
helpMsg
:
"stack [<depth> [<goroutine id>]]. Prints stack."
},
}
return
c
...
...
@@ -189,7 +190,7 @@ func goroutines(client service.Client, args ...string) error {
if
g
.
Function
!=
nil
{
fname
=
g
.
Function
.
Name
}
fmt
.
Printf
(
"Goroutine %d - %s:%d %s
\n
"
,
g
.
ID
,
g
.
File
,
g
.
Line
,
fname
)
fmt
.
Printf
(
"Goroutine %d - %s:%d %s
(%#v)
\n
"
,
g
.
ID
,
g
.
File
,
g
.
Line
,
fname
,
g
.
PC
)
}
return
nil
}
...
...
@@ -399,7 +400,7 @@ func info(client service.Client, args ...string) error {
data
=
filterVariables
(
vars
,
filter
)
default
:
return
fmt
.
Errorf
(
"unsupported info type, must be args, funcs, locals, sources
,
or vars"
)
return
fmt
.
Errorf
(
"unsupported info type, must be args, funcs, locals, sources or vars"
)
}
// sort and output data
...
...
@@ -411,6 +412,45 @@ func info(client service.Client, args ...string) error {
return
nil
}
func
stackCommand
(
client
service
.
Client
,
args
...
string
)
error
{
var
err
error
goroutineid
:=
-
1
depth
:=
10
switch
len
(
args
)
{
case
0
:
// nothing to do
case
2
:
goroutineid
,
err
=
strconv
.
Atoi
(
args
[
1
])
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Wrong argument: expected integer"
)
}
fallthrough
case
1
:
depth
,
err
=
strconv
.
Atoi
(
args
[
0
])
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Wrong argument: expected integer"
)
}
default
:
return
fmt
.
Errorf
(
"Wrong number of arguments to stack"
)
}
stack
,
err
:=
client
.
Stacktrace
(
goroutineid
,
depth
)
if
err
!=
nil
{
return
err
}
for
i
:=
range
stack
{
name
:=
"(nil)"
if
stack
[
i
]
.
Function
!=
nil
{
name
=
stack
[
i
]
.
Function
.
Name
}
fmt
.
Printf
(
"%d. %s
\n\t
%s:%d (%#v)
\n
"
,
i
,
name
,
stack
[
i
]
.
File
,
stack
[
i
]
.
Line
,
stack
[
i
]
.
PC
)
}
return
nil
}
func
printcontext
(
state
*
api
.
DebuggerState
)
error
{
if
state
.
CurrentThread
==
nil
{
fmt
.
Println
(
"No current thread available"
)
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录