diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9b7368d..ba00d9a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,9 +27,9 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. -**Kitex version:** +**Goref version:** -Please provide the version of Kitex you are using. +Please provide the version of goref you are using. **Environment:** diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1b96ef5..e693316 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ jobs: unit-benchmark-test: strategy: matrix: - go: [ "1.18", "1.19", "1.20", "1.21", "1.22" ] + go: [ "1.21", "1.22" ] os: [ X64 ] runs-on: ${{ matrix.os }} steps: diff --git a/.golangci.yaml b/.golangci.yaml index 71405b0..ec7a899 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,7 +3,6 @@ run: # include `vendor` `third_party` `testdata` `examples` `Godeps` `builtin` skip-dirs-use-default: true skip-dirs: - - kitex_gen skip-files: - ".*\\.mock\\.go$" # output configuration options diff --git a/.licenserc.yaml b/.licenserc.yaml index 6046e12..14862f5 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -7,4 +7,12 @@ header: - '**/*.go' - '**/*.s' + paths-ignore: + - pkg/proc/eval.go + - pkg/proc/mem.go + - pkg/proc/protobuf.go + - pkg/proc/region.go + - pkg/proc/variables.go + - testdata + comment: on-failure \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e60a59e..d37a2c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as o ## Bugs ### 1. How to Find Known Issues -We are using [Github Issues](https://github.com/cloudwego/kitex/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. +We are using [Github Issues](https://github.com/cloudwego/goref/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. ### 2. Reporting New Issues Providing a reduced test code is a recommended way for reporting issues. Then can placed in: @@ -23,12 +23,12 @@ Please do not report the safe disclosure of bugs to public issues. Contact us by ## Submit a Pull Request Before you submit your Pull Request (PR) consider the following guidelines: -1. Search [GitHub](https://github.com/cloudwego/kitex/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. +1. Search [GitHub](https://github.com/cloudwego/goref/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work. -3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the cloudwego/kitex repo. +3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the cloudwego/goref repo. 4. In your forked repository, make your changes in a new git branch: ``` - git checkout -b my-fix-branch develop + git checkout -b my-fix-branch main ``` 5. Create your patch, including appropriate test cases. 6. Follow our [Style Guides](#code-style-guides). @@ -38,7 +38,7 @@ Before you submit your Pull Request (PR) consider the following guidelines: ``` git push origin my-fix-branch ``` -9. In GitHub, send a pull request to `kitex:develop` +9. In GitHub, send a pull request to `goref:main` ## Contribution Prerequisites - Our development environment keeps up with [Go Official](https://golang.org/project/). diff --git a/README.md b/README.md index a46ae92..8e2228f 100644 --- a/README.md +++ b/README.md @@ -1 +1,44 @@ -# .github \ No newline at end of file +# Goref + +[![WebSite](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/) +[![License](https://img.shields.io/github/license/cloudwego/goref)](https://github.com/cloudwego/goref/blob/main/LICENSE-APACHE) + +Goref is a Go heap object reference analysis tool based on delve. +It can display the space and object count distribution of Go memory references, which is helpful for efficiently locating memory leak issues or viewing persistent heap objects to optimize GC overhead. + +## Installation + +Clone the git repository and build: + +``` +$ git clone https://github.com/cloudwego/goref +$ cd goref +$ go install github.com/cloudwego/goref/cmd/grf +``` + +> Supported go version to compile the command tool: go1.21 ~ go1.22. + +## Usage + +Attach to a running process with its PID, and then use go pprof tool to open the output file. + +``` +$ grf attach ${PID} +successfully output to `grf.out` +$ go tool pprof -http=:5079 ./grf.out +``` + +The opened HTML page displays the reference distribution of the heap memory. You can choose to view the "inuse space" or "inuse objects". + +It also supports analyzing core files, e.g. + +``` +$ grf core ${execfile} ${corefile} +successfully output to `grf.out` +``` + +> Supported go version for executable file: go1.17 ~ go1.22. + +## Credit + +Thanks to [Delve](https://github.com/go-delve/delve) for providing powerful golang debugger. \ No newline at end of file diff --git a/cmd/grf/cmds/commands.go b/cmd/grf/cmds/commands.go new file mode 100644 index 0000000..c7832b0 --- /dev/null +++ b/cmd/grf/cmds/commands.go @@ -0,0 +1,144 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmds + +import ( + "errors" + "fmt" + "os" + "strconv" + + "github.com/go-delve/delve/pkg/config" + "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/service/debugger" + "github.com/spf13/cobra" + + myproc "github.com/cloudwego/goref/pkg/proc" +) + +var ( + // rootCommand is the root of the command tree. + rootCommand *cobra.Command + + conf *config.Config + loadConfErr error + outFile string +) + +// New returns an initialized command tree. +func New(docCall bool) *cobra.Command { + // Config setup and load. + conf, loadConfErr = config.LoadConfig() + + // Main dlv root command. + rootCommand = &cobra.Command{ + Use: "grf", + Short: "Goref is a Go heap object reference analysis tool based on delve.", + Long: "Goref is a Go heap object reference analysis tool based on delve.", + } + rootCommand.CompletionOptions.DisableDefaultCmd = true + + // 'attach' subcommand. + attachCommand := &cobra.Command{ + Use: "attach pid [executable]", + Short: "Attach to running process and begin scanning.", + Long: `Attach to an already running process and begin scanning its memory. + +This command will cause Goref to take control of an already running process and begin scanning object references. +You'll have to wait for goref until it outputs 'successfully output to ...', or kill it to terminate scanning. +`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("you must provide a PID") + } + return nil + }, + Run: attachCmd, + } + attachCommand.Flags().StringVarP(&outFile, "out", "o", "grf.out", "output file name") + rootCommand.AddCommand(attachCommand) + + coreCommand := &cobra.Command{ + Use: "core ", + Short: "Scan a core dump.", + Long: `Scan a core dump (only supports linux and windows core dumps). + +The core command will open the specified core file and the associated executable and begin scanning object references. +You'll have to wait for goref until it outputs 'successfully output to ...', or kill it to terminate scanning.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("you must provide a core file and an executable") + } + return nil + }, + Run: coreCmd, + } + coreCommand.Flags().StringVarP(&outFile, "out", "o", "grf.out", "output file name") + rootCommand.AddCommand(coreCommand) + + return rootCommand +} + +func attachCmd(_ *cobra.Command, args []string) { + var pid int + var exeFile string + if len(args) > 0 { + var err error + pid, err = strconv.Atoi(args[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) + os.Exit(1) + } + } + if len(args) > 1 { + exeFile = args[1] + } + os.Exit(execute(pid, exeFile, "", outFile, conf)) +} + +func coreCmd(_ *cobra.Command, args []string) { + os.Exit(execute(0, args[0], args[1], outFile, conf)) +} + +func execute(attachPid int, exeFile, coreFile, outFile string, conf *config.Config) int { + if loadConfErr != nil { + logflags.DebuggerLogger().Errorf("%v", loadConfErr) + } + + dConf := debugger.Config{ + AttachPid: attachPid, + Backend: "default", + CoreFile: coreFile, + DebugInfoDirectories: conf.DebugInfoDirectories, + AttachWaitFor: "", + AttachWaitForInterval: 1, + AttachWaitForDuration: 0, + } + var args []string + if exeFile != "" { + args = []string{exeFile} + } + dbg, err := debugger.New(&dConf, args) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + return 1 + } + t := dbg.Target() + if err = myproc.ObjectReference(t, outFile); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + return 1 + } + return 0 +} diff --git a/cmd/grf/main.go b/cmd/grf/main.go new file mode 100644 index 0000000..baf94ca --- /dev/null +++ b/cmd/grf/main.go @@ -0,0 +1,23 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "github.com/cloudwego/goref/cmd/grf/cmds" +) + +func main() { + cmds.New(false).Execute() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..77c70bb --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module github.com/cloudwego/goref + +go 1.21 + +toolchain go1.22.2 + +require ( + github.com/go-delve/delve v1.22.2-0.20240701043435-faac701e9f79 + github.com/modern-go/reflect2 v1.0.2 + github.com/spf13/cobra v1.8.0 +) + +require ( + github.com/cilium/ebpf v0.11.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/arch v0.6.0 // indirect + golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect + golang.org/x/sys v0.17.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9b3e7f4 --- /dev/null +++ b/go.sum @@ -0,0 +1,56 @@ +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= +github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4= +github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-delve/delve v1.22.2-0.20240701043435-faac701e9f79 h1:wDUy5rTtpM5oCscyPYQCDiTjWNNdN2j+vNpEHdKoUI0= +github.com/go-delve/delve v1.22.2-0.20240701043435-faac701e9f79/go.mod h1:iS7XgxZVcrCf9piPKK1RT21pEbjtgzrYlPDl2uja6gQ= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= +golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/licenses/LICENSE-cobra b/licenses/LICENSE-cobra new file mode 100644 index 0000000..cbfdef8 --- /dev/null +++ b/licenses/LICENSE-cobra @@ -0,0 +1,174 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. \ No newline at end of file diff --git a/licenses/LICENSE-delve b/licenses/LICENSE-delve new file mode 100644 index 0000000..5788ae8 --- /dev/null +++ b/licenses/LICENSE-delve @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 Derek Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/licenses/LICENSE-reflect2 b/licenses/LICENSE-reflect2 new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/licenses/LICENSE-reflect2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/pkg/proc/address.go b/pkg/proc/address.go new file mode 100644 index 0000000..0ed093c --- /dev/null +++ b/pkg/proc/address.go @@ -0,0 +1,28 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +// Address provides a simple encapsulation of address operations. +type Address uint64 + +// Sub subtracts b from a. Requires a >= b. +func (a Address) Sub(b Address) int64 { + return int64(a - b) +} + +// Add adds x to address a. +func (a Address) Add(x int64) Address { + return a + Address(x) +} diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go new file mode 100644 index 0000000..37040a2 --- /dev/null +++ b/pkg/proc/eval.go @@ -0,0 +1,263 @@ +// Copyright (c) 2014 Derek Parker +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// This file may have been modified by CloudWeGo authors. All CloudWeGo +// Modifications are Copyright 2024 CloudWeGo Authors. + +package proc + +import ( + "debug/dwarf" + "errors" + "fmt" + "sort" + "strings" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/dwarf/reader" + "github.com/go-delve/delve/pkg/goversion" + "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc" +) + +const ( + goDictionaryName = ".dict" + goClosurePtr = ".closureptr" +) + +const fakeAddressUnresolv = 0xbeed000000000000 + +type myEvalScope struct { + proc.EvalScope + + dictAddr uint64 // dictionary address for instantiated generic functions + + // enclosingRangeScopes []*proc.EvalScope + // rangeFrames []proc.Stackframe +} + +func (scope *myEvalScope) Locals(mds []proc.ModuleData) ([]*ReferenceVariable, error) { + // var scopes [][]*Variable + vars0, err := scope.simpleLocals(mds) + if err != nil { + return nil, err + } + return vars0, nil + // TODO: support range-over-func + /* + if scope.Fn.extra(scope.BinInfo).rangeParent == nil || scope.target == nil || scope.g == nil { + return vars0, nil + } + + scopes = append(scopes, vars0) + + if scope.rangeFrames == nil { + scope.rangeFrames, err = rangeFuncStackTrace(scope.target, scope.g) + if err != nil { + return nil, err + } + scope.rangeFrames = scope.rangeFrames[1:] + scope.enclosingRangeScopes = make([]*EvalScope, len(scope.rangeFrames)) + } + for i, scope2 := range scope.enclosingRangeScopes { + if i == len(scope.enclosingRangeScopes)-1 { + // Last one is the caller frame, we shouldn't check it + break + } + if scope2 == nil { + scope2 = FrameToScope(scope.target, scope.target.Memory(), scope.g, scope.threadID, scope.rangeFrames[i:]...) + scope.enclosingRangeScopes[i] = scope2 + } + vars, err := scope2.simpleLocals0(flags, mds) + if err != nil { + return nil, err + } + scopes = append(scopes, vars) + } + + vars := []*Variable{} + for i := len(scopes) - 1; i >= 0; i-- { + vars = append(vars, scopes[i]...) + } + + // Apply shadowning + lvn := map[string]*Variable{} + for _, v := range vars { + if otherv := lvn[v.Name]; otherv != nil { + otherv.Flags |= VariableShadowed + } + lvn[v.Name] = v + } + return vars, nil + */ +} + +func (scope *myEvalScope) simpleLocals(mds []proc.ModuleData) ([]*ReferenceVariable, error) { + if scope.Fn == nil { + return nil, errors.New("unable to find function context") + } + + if image(&scope.EvalScope).Stripped() { + return nil, errors.New("unable to find locals: no debug information present in binary") + } + + dwarfTree, err := getDwarfTree(image(&scope.EvalScope), getFunctionOffset(scope.Fn)) + if err != nil { + return nil, err + } + + variablesFlags := reader.VariablesOnlyVisible + if scope.BinInfo.Producer() != "" && goversion.ProducerAfterOrEqual(scope.BinInfo.Producer(), 1, 15) { + variablesFlags |= reader.VariablesTrustDeclLine + } + + varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, variablesFlags) + + // look for dictionary entry + if scope.dictAddr == 0 { + for _, entry := range varEntries { + name, _ := entry.Val(dwarf.AttrName).(string) + if name == goDictionaryName { + dictVar, err := extractVarInfoFromEntry(scope.BinInfo, image(&scope.EvalScope), scope.Regs, scope.Mem, entry.Tree, 0, mds) + if err != nil { + logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err) + } else { + scope.dictAddr, err = readUintRaw(dictVar.mem, uint64(dictVar.Addr), int64(scope.BinInfo.Arch.PtrSize())) + if err != nil { + logflags.DebuggerLogger().Errorf("could not load %s variable: %v", name, err) + } + } + break + } + } + } + + vars := make([]*ReferenceVariable, 0, len(varEntries)) + depths := make([]int, 0, len(varEntries)) + for _, entry := range varEntries { + name, _ := entry.Val(dwarf.AttrName).(string) + if name == goDictionaryName || name == goClosurePtr || strings.HasPrefix(name, "#state") || strings.HasPrefix(name, "&#state") || strings.HasPrefix(name, "#next") || strings.HasPrefix(name, "&#next") || strings.HasPrefix(name, "#yield") { + continue + } + if rangeParentName(scope.Fn) != "" { + // Skip return values and closure variables for range-over-func closure bodies + if strings.HasPrefix(name, "~") { + continue + } + if entry.Val(godwarf.AttrGoClosureOffset) != nil { + continue + } + } + val, err := extractVarInfoFromEntry(scope.BinInfo, image(&scope.EvalScope), scope.Regs, scope.Mem, entry.Tree, scope.dictAddr, mds) + if err != nil { + // skip variables that we can't parse yet + continue + } + vars = append(vars, val) + depth := entry.Depth + if entry.Tag == dwarf.TagFormalParameter { + if depth <= 1 { + depth = 0 + } + } + depths = append(depths, depth) + } + if len(vars) == 0 { + return vars, nil + } + sort.Stable(&variablesByDepthAndDeclLine{vars, depths}) + return vars, nil +} + +// Extracts the name and type of a variable from a dwarf entry +// then executes the instructions given in the DW_AT_location attribute to grab the variable's address +func extractVarInfoFromEntry(bi *proc.BinaryInfo, image *proc.Image, regs op.DwarfRegisters, mem proc.MemoryReadWriter, entry *godwarf.Tree, dictAddr uint64, mds []proc.ModuleData) (*ReferenceVariable, error) { + if entry.Tag != dwarf.TagFormalParameter && entry.Tag != dwarf.TagVariable { + return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", entry.Tag.String()) + } + + n, t, err := readVarEntry(entry, image) + if err != nil { + return nil, err + } + + t, err = resolveParametricType(bi, mem, t, dictAddr, mds) + if err != nil { + // Log the error, keep going with t, which will be the shape type + logflags.DebuggerLogger().Errorf("could not resolve parametric type of %s: %v", n, err) + } + + addr, pieces, _, _ := bi.Location(entry, dwarf.AttrLocation, regs.PC(), regs, mem) + uaddr := uint64(addr) + if pieces != nil { + cmem, _ := proc.CreateCompositeMemory(mem, bi.Arch, regs, pieces, t.Common().ByteSize) + if cmem != nil { + uaddr = fakeAddressUnresolv + mem = cmem + } + } + + v := newReferenceVariable(Address(uaddr), n, resolveTypedef(t), mem, nil) + return v, nil +} + +// resolveParametricType returns the real type of t if t is a parametric +// type, by reading the correct dictionary entry. +func resolveParametricType(bi *proc.BinaryInfo, mem proc.MemoryReadWriter, t godwarf.Type, dictAddr uint64, mds []proc.ModuleData) (godwarf.Type, error) { + ptyp, _ := t.(*godwarf.ParametricType) + if ptyp == nil { + return t, nil + } + if dictAddr == 0 { + return ptyp.TypedefType.Type, errors.New("parametric type without a dictionary") + } + rtypeAddr, err := readUintRaw(mem, dictAddr+uint64(ptyp.DictIndex*int64(bi.Arch.PtrSize())), int64(bi.Arch.PtrSize())) + if err != nil { + return ptyp.TypedefType.Type, err + } + runtimeType, err := findType(bi, runtimeTypeTypename(bi)) + if err != nil { + return ptyp.TypedefType.Type, err + } + _type := newVariable("", rtypeAddr, runtimeType, bi, mem) + + typ, _, err := proc.RuntimeTypeToDIE(_type, 0, mds) + if err != nil { + return ptyp.TypedefType.Type, err + } + + return typ, nil +} + +func runtimeTypeTypename(bi *proc.BinaryInfo) string { + if goversion.ProducerAfterOrEqual(bi.Producer(), 1, 21) { + return "internal/abi.Type" + } + return "runtime._type" +} + +type variablesByDepthAndDeclLine struct { + vars []*ReferenceVariable + depths []int +} + +func (v *variablesByDepthAndDeclLine) Len() int { return len(v.vars) } + +func (v *variablesByDepthAndDeclLine) Less(i, j int) bool { + return v.depths[i] < v.depths[j] +} + +func (v *variablesByDepthAndDeclLine) Swap(i, j int) { + v.depths[i], v.depths[j] = v.depths[j], v.depths[i] + v.vars[i], v.vars[j] = v.vars[j], v.vars[i] +} diff --git a/pkg/proc/heap.go b/pkg/proc/heap.go new file mode 100644 index 0000000..d1a0d0a --- /dev/null +++ b/pkg/proc/heap.go @@ -0,0 +1,591 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "errors" + "go/constant" + "math" + "math/bits" + + "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc" +) + +type spanInfo struct { + base Address // start address of the span + elemSize int64 // size of objects in the span + spanSize int64 // size of the span + visitMask []uint64 // 64 * n mark bits, one for every 8 bytes + ptrMask []uint64 // 64 * n ptr bits, one for every 8 bytes + + spanclass spanClass // alloc header span + largeTypeAddr uint64 // for large type +} + +// marks the pointer, return true of not marked before. +func (sp *spanInfo) mark(addr Address) bool { + offset := addr.Sub(sp.base) + if sp.visitMask[offset/8/64]&(1<<(offset/8%64)) != 0 { + return false + } else { + sp.visitMask[offset/8/64] |= 1 << (offset / 8 % 64) + return true + } +} + +type segment struct { + start, end Address + visitMask []uint64 +} + +func (s *segment) init(start, end Address) { + s.start, s.end = start, end + maskLen := (end - start) / 8 / 64 + if (end-start)/8%64 != 0 { + maskLen += 1 + } + s.visitMask = make([]uint64, maskLen) +} + +func (s *segment) mark(addr Address) (success bool) { + if addr >= s.start && addr < s.end { + offset := addr.Sub(s.start) + if s.visitMask[offset/8/64]&(1<<(offset/8%64)) != 0 { + return false + } else { + s.visitMask[offset/8/64] |= 1 << (offset / 8 % 64) + return true + } + } + return false +} + +type segments []*segment + +func (ss segments) mark(addr Address) (success bool, seg *segment) { + if len(ss) == 1 { + // most of scene + if ss[0].mark(addr) { + return true, ss[0] + } + return false, nil + } + for _, seg = range ss { + if seg.mark(addr) { + return true, seg + } + } + return false, nil +} + +// stack inherits segment. +type stack struct { + segment +} + +// HeapScope contains the proc info for this round of scanning. +type HeapScope struct { + // runtime constants + pageSize int64 + heapArenaBytes int64 + pagesPerArena int64 + arenaL1Bits int64 + arenaL2Bits int64 + arenaBaseOffset int64 + + // data/bss segments + data, bss segments + + // enable alloc header + enableAllocHeader bool + minSizeForMallocHeader int64 + + // arena info map + arenaInfo []*[]*[]*spanInfo + + finalizers []finalizer + + mds []proc.ModuleData + + mem proc.MemoryReadWriter + bi *proc.BinaryInfo + scope *proc.EvalScope + + finalMarks []finalMarkParam +} + +func (s *HeapScope) readHeap() error { + rdr := s.bi.Images[0].DwarfReader() + if rdr == nil { + return errors.New("error dwarf reader is nil") + } + tmp, err := s.scope.EvalExpression("runtime.mheap_", loadSingleValue) + if err != nil { + return err + } + mheap := toRegion(tmp, s.bi) + // read runtime constants + s.pageSize = s.rtConstant("_PageSize") + spanInUse := uint8(s.rtConstant("_MSpanInUse")) + if spanInUse == 0 { + spanInUse = uint8(s.rtConstant("mSpanInUse")) + } + s.heapArenaBytes = s.rtConstant("heapArenaBytes") + s.pagesPerArena = s.heapArenaBytes / s.pageSize + kindSpecialFinalizer := uint8(s.rtConstant("_KindSpecialFinalizer")) + s.arenaBaseOffset = s.getArenaBaseOffset() + s.arenaL1Bits, s.arenaL2Bits = s.rtConstant("arenaL1Bits"), s.rtConstant("arenaL2Bits") + s.minSizeForMallocHeader = s.rtConstant("minSizeForMallocHeader") + + // start read all spans + spans, spanInfos := s.readAllSpans(mheap.Field("allspans").Array(), spanInUse, kindSpecialFinalizer) + + // start read arenas + if s.readArenas(mheap) { + return nil + } + + // read typed pointers when enabled alloc header + s.readTypePointers(spans, spanInfos) + + // read firstmoduledata + return s.readModuleData() +} + +func (s *HeapScope) readAllSpans(allspans *region, spanInUse, kindSpecialFinalizer uint8) (spans []*region, spanInfos []*spanInfo) { + // read all spans + n := allspans.ArrayLen() + to := ®ion{} + for i := int64(0); i < n; i++ { + allspans.ArrayIndex(i, to) + sp := to.Deref() + base := Address(sp.Field("startAddr").Uintptr()) + elemSize := int64(sp.Field("elemsize").Uintptr()) + spanSize := int64(sp.Field("npages").Uintptr()) * s.pageSize + st := sp.Field("state") + if st.IsStruct() && st.HasField("s") { // go1.14+ + st = st.Field("s") + } + if st.IsStruct() && st.HasField("value") { // go1.20+ + st = st.Field("value") + } + if st.Uint8() != spanInUse { + continue + } + maskLen := spanSize / 8 / 64 + if spanSize/8%64 != 0 { + maskLen += 1 + } + spi := &spanInfo{ + base: base, elemSize: elemSize, spanSize: spanSize, + visitMask: make([]uint64, maskLen), ptrMask: make([]uint64, maskLen), + } + max := base.Add(spanSize) + for addr := base; addr < max; addr = addr.Add(s.pageSize) { + s.allocSpan(addr, spi) + } + if err := s.addSpecial(sp, spi, kindSpecialFinalizer); err != nil { + logflags.DebuggerLogger().Errorf("%v", err) + } + // for go 1.22 with allocation header + spans = append(spans, sp) + spanInfos = append(spanInfos, spi) + } + return +} + +func (s *HeapScope) heapBitsInSpan(elemSize int64) bool { + return elemSize <= s.minSizeForMallocHeader +} + +func (s *HeapScope) readTypePointers(spans []*region, spanInfos []*spanInfo) { + for i, sp := range spans { + spi := spanInfos[i] + spc := spanClass(sp.Field("spanclass").Uint8()) + spi.spanclass = spc + if spc.noscan() { + continue + } + if s.heapBitsInSpan(spi.elemSize) { + bitmapSize := spi.spanSize / 8 / 8 + readUint64Array(s.mem, uint64(spi.base.Add(spi.spanSize-bitmapSize)), spi.ptrMask) + continue + } + // with alloc headers + if spc.sizeclass() == 0 { + largeTypeAddr := sp.Field("largeType").Address() + spi.largeTypeAddr = uint64(largeTypeAddr) + } + } +} + +func (s *HeapScope) readArenas(mheap *region) (success bool) { + arenaSize := s.rtConstant("heapArenaBytes") + level1Table := mheap.Field("arenas") + level1size := level1Table.ArrayLen() + to := ®ion{} + var readBitmapFunc func(heapArena *region, min Address) + for level1 := int64(0); level1 < level1size; level1++ { + level1Table.ArrayIndex(level1, to) + if to.Address() == 0 { + continue + } + level2table := to.Deref() + level2size := level2table.ArrayLen() + for level2 := int64(0); level2 < level2size; level2++ { + level2table.ArrayIndex(level2, to) + if to.Address() == 0 { + continue + } + heapArena := to.Deref() + min := Address(arenaSize*(level2+level1*level2size) - s.arenaBaseOffset) + if readBitmapFunc == nil { + if readBitmapFunc = s.readBitmapFunc(heapArena); readBitmapFunc == nil { + return false + } + } + readBitmapFunc(heapArena, min) + } + } + return true +} + +func (s *HeapScope) readBitmapFunc(heapArena *region) func(heapArena *region, min Address) { + // read bitmap + if heapArena.HasField("bitmap") { // Before go 1.22 + if oneBitBitmap := heapArena.HasField("noMorePtrs"); oneBitBitmap { // Starting in go 1.20 + return func(heapArena *region, min Address) { + s.readOneBitBitmap(heapArena.Field("bitmap"), min) + } + } else { + return func(heapArena *region, min Address) { + s.readMultiBitBitmap(heapArena.Field("bitmap"), min) + } + } + } else if heapArena.HasField("heapArenaPtrScalar") && heapArena.Field("heapArenaPtrScalar").HasField("bitmap") { // go 1.22 without allocation headers + return func(heapArena *region, min Address) { + s.readOneBitBitmap(heapArena.Field("heapArenaPtrScalar").Field("bitmap"), min) + } + } else { // go 1.22 with allocation headers + s.enableAllocHeader = true + return nil + } +} + +// base must be the base address of an object in then span +func (s *HeapScope) copyGCMask(sp *spanInfo, base Address) Address { + if !s.enableAllocHeader { + return base + } + if sp.spanclass.noscan() { + return base + } + if s.heapBitsInSpan(sp.elemSize) { + return base + } + if sp.spanclass.sizeclass() != 0 { + // alloc type in header + typeAddr, _ := readUintRaw(s.mem, uint64(base), 8) + s.readType(sp, Address(typeAddr), base.Add(8), base.Add(sp.elemSize)) + return base.Add(8) + } else { + // large type + s.readType(sp, Address(sp.largeTypeAddr), base, base.Add(sp.elemSize)) + return base + } +} + +func (s *HeapScope) readType(sp *spanInfo, typeAddr, addr, end Address) { + var typeSize, ptrBytes int64 + var gcDataAddr Address + mem := cacheMemory(s.mem, uint64(typeAddr), int(gcDataOffset+8)) + if typeSize_, err := readUintRaw(mem, uint64(typeAddr.Add(sizeOffset)), 8); err != nil { + return + } else { + typeSize = int64(typeSize_) + } + if ptrBytes_, err := readUintRaw(mem, uint64(typeAddr.Add(ptrBytesOffset)), 8); err != nil { + return + } else { + ptrBytes = int64(ptrBytes_) + } + if gcDataAddr_, err := readUintRaw(mem, uint64(typeAddr.Add(gcDataOffset)), 8); err != nil { + return + } else { + gcDataAddr = Address(gcDataAddr_) + bLen := int(math.Ceil(float64(ptrBytes)/512)) * 512 + mem = cacheMemory(s.mem, uint64(gcDataAddr), bLen/64) + } + elem := addr + for { + if addr >= elem.Add(ptrBytes) { + // No more ptrs, copy the next element. + // Maybe overflow beyond the real object, but doesn't affect the correctness. + elem = elem.Add(typeSize) + addr = elem + } + if addr >= end { + break + } + mask, _ := readUintRaw(mem, uint64(gcDataAddr.Add(addr.Sub(elem)/64)), 8) + var headBits int64 + if addr.Add(8*64) > end { + headBits = (end.Sub(addr)) / 8 + mask &^= ((1 << (64 - headBits)) - 1) << headBits + } + offset := addr.Sub(sp.base) + idx := offset / 8 / 64 + bit := offset / 8 % 64 + sp.ptrMask[idx] |= mask << bit + if idx+1 < int64(len(sp.ptrMask)) { + // copy remaining mask to next + sp.ptrMask[idx+1] |= mask >> (64 - bit) + } + // next + addr = addr.Add(8 * 64) + } +} + +// Read a one-bit bitmap (Go 1.20+), recording the heap pointers. +func (s *HeapScope) readOneBitBitmap(bitmap *region, min Address) { + n := bitmap.ArrayLen() + to := ®ion{} + for i := int64(0); i < n; i++ { + bitmap.ArrayIndex(i, to) + m := to.Uintptr() + var j int64 + for { + j += int64(bits.TrailingZeros64(m >> j)) + if j >= 64 { + break + } + s.setHeapPtr(min.Add((i*64 + j) * 8)) + j++ + } + } +} + +// TODO: use bitmapMask to speed up memory lookup. +// const bitmapMask uint64 = 0xf0f0f0f0f0f0f0f0 + +// Read a multi-bit bitmap (Go 1.11-1.20), recording the heap pointers. +func (s *HeapScope) readMultiBitBitmap(bitmap *region, min Address) { + ptrSize := int64(s.bi.Arch.PtrSize()) + n := bitmap.ArrayLen() + to := ®ion{} + for i := int64(0); i < n; i++ { + // batch read 8 bytes, which corresponds to 32 pointers. + bitmap.ArrayIndex(i, to) + m := to.Uint8() + for j := int64(0); j < 4; j++ { + if m>>uint(j)&1 != 0 { + s.setHeapPtr(min.Add((i*4 + j) * ptrSize)) + } + } + } +} + +func (s *HeapScope) indexes(addr Address) (l1, l2, idx uint) { + if s.arenaL1Bits == 0 { + l2 = s.arenaIndex(uintptr(addr)) + } else { + ri := s.arenaIndex(uintptr(addr)) + l1 = ri >> s.arenaL2Bits + l2 = ri & (1<= uint(len(s.arenaInfo)) { + return + } + l1Info := s.arenaInfo[l1] + if l1Info == nil { + tmp := make([]*[]*spanInfo, 1<= uint(len(*l1Info)) { + return + } + arena := (*l1Info)[l2] + if arena == nil { + tmp := make([]*spanInfo, s.pagesPerArena) + arena = &tmp + (*s.arenaInfo[l1])[l2] = arena + } + if idx >= uint(len(*arena)) { + return + } + if (*arena)[idx] == nil { + (*arena)[idx] = sp + } +} + +func (s *HeapScope) findSpanAndBase(addr Address) (sp *spanInfo, base Address) { + sp = s.spanOf(addr) + if sp == nil { + return + } + offset := addr.Sub(sp.base) + base = sp.base.Add(offset / sp.elemSize * sp.elemSize) + return +} + +func (s *HeapScope) setHeapPtr(a Address) { + sp := s.spanOf(a) + if sp == nil { + return + } + offset := a.Sub(sp.base) + sp.ptrMask[offset/8/64] |= uint64(1) << (offset / 8 % 64) +} + +type heapBits struct { + base Address // heap base + addr Address // iterator address + end Address // cannot reach end + sp *spanInfo // span info +} + +func newHeapBits(base, end Address, sp *spanInfo) *heapBits { + return &heapBits{base: base, addr: base, end: end, sp: sp} +} + +// resetGCMask will reset ptrMask corresponding to the address, +// which will never be marked again by the finalMark. +func (hb *heapBits) resetGCMask(addr Address) { + if hb == nil { + return + } + // TODO: check gc mask + offset := addr.Sub(hb.sp.base) + hb.sp.ptrMask[offset/8/64] &= ^(1 << (offset / 8 % 64)) +} + +// nextPtr returns next ptr address starts from 'addr', returns 0 if not found. +// If ack == true, the 'addr' will automatically increment to the next +// starting address to be searched. +func (hb *heapBits) nextPtr(ack bool) Address { + if hb == nil { + return 0 + } + startOffset, endOffset := hb.addr.Sub(hb.sp.base), hb.end.Sub(hb.sp.base) + if startOffset >= endOffset || startOffset < 0 || endOffset > hb.sp.spanSize { + return 0 + } + for startOffset < endOffset { + ptrIdx := startOffset / 8 / 64 + i := startOffset / 8 % 64 + j := int64(bits.TrailingZeros64(hb.sp.ptrMask[ptrIdx] >> i)) + if j == 64 { + // search the next ptr + startOffset = (ptrIdx + 1) * 64 * 8 + continue + } + addr := hb.sp.base.Add(startOffset + j*8) + if addr >= hb.end { + return 0 + } + if ack { + hb.addr = addr.Add(8) + } + return addr + } + return 0 +} + +func (s *HeapScope) spanOf(addr Address) *spanInfo { + l1, l2, idx := s.indexes(addr) + if l1 < uint(len(s.arenaInfo)) { + l1Info := s.arenaInfo[l1] + if l1Info != nil && l2 < uint(len(*l1Info)) { + l2Info := (*l1Info)[l2] + if l2Info != nil && idx < uint(len(*l2Info)) { + return (*l2Info)[idx] + } + } + } + return nil +} + +func (s *HeapScope) arenaIndex(p uintptr) uint { + return uint((p + uintptr(s.arenaBaseOffset)) / uintptr(s.heapArenaBytes)) +} + +func (s *HeapScope) readModuleData() error { + tmp, err := s.scope.EvalExpression("runtime.firstmoduledata", loadSingleValue) + if err != nil { + return err + } + firstmoduledata := toRegion(tmp, s.bi) + for md := firstmoduledata; md.a != 0; md = md.Field("next").Deref() { + var data, bss segment + data.init(Address(firstmoduledata.Field("data").Uintptr()), Address(firstmoduledata.Field("edata").Uintptr())) + bss.init(Address(firstmoduledata.Field("bss").Uintptr()), Address(firstmoduledata.Field("ebss").Uintptr())) + s.data = append(s.data, &data) + s.bss = append(s.bss, &bss) + } + return nil +} + +type finalizer struct { + p Address // finalized pointer + fn Address // finalizer function, always 8 bytes +} + +func (s *HeapScope) addSpecial(sp *region, spi *spanInfo, kindSpecialFinalizer uint8) error { + // Process special records. + spty, _ := findType(s.bi, "runtime.specialfinalizer") + for special := sp.Field("specials"); special.Address() != 0; special = special.Field("next") { + special = special.Deref() // *special to special + if special.Field("kind").Uint8() != kindSpecialFinalizer { + // All other specials (just profile records) can't point into the heap. + continue + } + var fin finalizer + p := spi.base.Add(int64(special.Field("offset").Uint16()) / spi.elemSize * spi.elemSize) + fin.p = p + spf := *special + spf.typ = spty + fin.fn = spf.Field("fn").a + s.finalizers = append(s.finalizers, fin) + } + return nil +} + +func (s *HeapScope) getArenaBaseOffset() int64 { + x, _ := s.scope.EvalExpression("runtime.arenaBaseOffsetUintptr", loadSingleValue) + // arenaBaseOffset changed sign in 1.15. Callers treat this + // value as it was specified in 1.14, so we negate it here. + xv, _ := constant.Int64Val(x.Value) + return -xv +} + +func (s *HeapScope) rtConstant(name string) int64 { + x, _ := s.scope.EvalExpression("runtime."+name, loadSingleValue) + if x != nil { + v, _ := constant.Int64Val(x.Value) + return v + } + return 0 +} diff --git a/pkg/proc/heap_test.go b/pkg/proc/heap_test.go new file mode 100644 index 0000000..2a2f530 --- /dev/null +++ b/pkg/proc/heap_test.go @@ -0,0 +1,42 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import "testing" + +func TestHeapBits(t *testing.T) { + hb := newHeapBits(0, 1024, &spanInfo{ + spanSize: 1024, + ptrMask: make([]uint64, 2), + }) + // set 16, 72, 208, 504, 928 as pointer + offsets := []int64{16, 72, 208, 504, 928} + for _, offset := range offsets { + hb.sp.ptrMask[offset/8/64] |= 1 << (offset / 8 % 64) + } + for i, offset := range offsets { + var nextOffset int64 + if i < len(offsets)-1 { + nextOffset = offsets[i+1] + } + if hb.nextPtr(false) != Address(offset) { + t.Fatalf("not %d", offset) + } + hb.resetGCMask(Address(offset)) + if hb.nextPtr(false) != Address(nextOffset) { + t.Fatalf("not %d", nextOffset) + } + } +} diff --git a/pkg/proc/mem.go b/pkg/proc/mem.go new file mode 100644 index 0000000..0769f6c --- /dev/null +++ b/pkg/proc/mem.go @@ -0,0 +1,80 @@ +// Copyright (c) 2014 Derek Parker +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// This file may have been modified by CloudWeGo authors. All CloudWeGo +// Modifications are Copyright 2024 CloudWeGo Authors. + +package proc + +import ( + "github.com/go-delve/delve/pkg/proc" +) + +const cacheEnabled = true + +type memCache struct { + loaded bool + cacheAddr uint64 + cache []byte + mem proc.MemoryReadWriter +} + +func (m *memCache) contains(addr uint64, size int) bool { + end := addr + uint64(size) + if end < addr { + // overflow + return false + } + return addr >= m.cacheAddr && end <= m.cacheAddr+uint64(len(m.cache)) +} + +func (m *memCache) ReadMemory(data []byte, addr uint64) (n int, err error) { + if m.contains(addr, len(data)) { + if !m.loaded { + _, err := m.mem.ReadMemory(m.cache, m.cacheAddr) + if err != nil { + return 0, err + } + m.loaded = true + } + copy(data, m.cache[addr-m.cacheAddr:]) + return len(data), nil + } + + return m.mem.ReadMemory(data, addr) +} + +func (m *memCache) WriteMemory(addr uint64, data []byte) (written int, err error) { + return m.mem.WriteMemory(addr, data) +} + +func cacheMemory(mem proc.MemoryReadWriter, addr uint64, size int) proc.MemoryReadWriter { + if !cacheEnabled { + return mem + } + if size <= 0 { + return mem + } + if addr+uint64(size) < addr { + // overflow + return mem + } + switch cacheMem := mem.(type) { + case *memCache: + if cacheMem.contains(addr, size) { + return mem + } else { + return &memCache{false, addr, make([]byte, size), cacheMem.mem} + } + } + return &memCache{false, addr, make([]byte, size), mem} +} diff --git a/pkg/proc/objects.go b/pkg/proc/objects.go new file mode 100644 index 0000000..8850cfd --- /dev/null +++ b/pkg/proc/objects.go @@ -0,0 +1,471 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "log" + "os" + "reflect" + "regexp" + "strconv" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc" +) + +const maxRefDepth = 256 + +type ObjRefScope struct { + *HeapScope + + pb *profileBuilder + + // maybe nil + g *stack +} + +func (s *ObjRefScope) findObject(addr Address, typ godwarf.Type, mem proc.MemoryReadWriter) (v *ReferenceVariable) { + sp, base := s.findSpanAndBase(addr) + if sp == nil { + // not in heap + var seg *segment + var suc bool + if suc, seg = s.bss.mark(addr); suc { + // in bss segment + } else if suc, seg = s.data.mark(addr); suc { + // in data segment + } else if s.g != nil && s.g.mark(addr) { + // in g stack + seg = &s.g.segment + } + if seg != nil { + if addr.Add(typ.Size()) > seg.end { + // There is an unsafe conversion, it is certain that another root object + // is referencing the memory, so there is no need to scan this object. + return + } + // TODO: using stackmap and gcbssmask + v = newReferenceVariable(addr, "", resolveTypedef(typ), mem, nil) + } + return + } + // Find mark bit + if !sp.mark(base) { + return // already found + } + realBase := s.copyGCMask(sp, base) + + // heap bits searching + hb := newHeapBits(realBase, base.Add(sp.elemSize), sp) + if hb.nextPtr(false) != 0 { + // has pointer, cache mem + mem = cacheMemory(mem, uint64(base), int(sp.elemSize)) + } + v = newReferenceVariableWithSizeAndCount(addr, "", resolveTypedef(typ), mem, hb, sp.elemSize, 1) + return +} + +func (s *HeapScope) markObject(addr Address, mem proc.MemoryReadWriter) (size, count int64) { + sp, base := s.findSpanAndBase(addr) + if sp == nil { + return // not found + } + // Find mark bit + if !sp.mark(base) { + return // already found + } + realBase := s.copyGCMask(sp, base) + size, count = sp.elemSize, 1 + hb := newHeapBits(realBase, base.Add(sp.elemSize), sp) + if hb.nextPtr(false) != 0 { + // has pointer, cache mem + mem = cacheMemory(mem, uint64(base), int(sp.elemSize)) + } + for { + ptr := hb.nextPtr(true) + if ptr == 0 { + break + } + nptr, err := readUintRaw(mem, uint64(ptr), int64(s.bi.Arch.PtrSize())) + if err != nil { + continue + } + size_, count_ := s.markObject(Address(nptr), mem) + size += size_ + count += count_ + } + return +} + +func (s *ObjRefScope) record(idx *pprofIndex, size, count int64) { + if size == 0 && count == 0 { + return + } + s.pb.addReference(idx.indexes(), count, size) +} + +type finalMarkParam struct { + idx *pprofIndex + hb *heapBits +} + +func (s *ObjRefScope) finalMark(idx *pprofIndex, hb *heapBits) { + var ptr Address + var size, count int64 + for { + ptr = hb.nextPtr(true) + if ptr == 0 { + break + } + ptr, err := readUintRaw(s.mem, uint64(ptr), int64(s.bi.Arch.PtrSize())) + if err != nil { + continue + } + size_, count_ := s.markObject(Address(ptr), s.mem) + size += size_ + count += count_ + } + s.record(idx, size, count) +} + +// findRef finds sub refs of x, and records them to pprof buffer. +func (s *ObjRefScope) findRef(x *ReferenceVariable, idx *pprofIndex) { + if x.Name != "" { + // For array elem / map kv / struct field type, record them. + if idx != nil && idx.depth >= maxRefDepth { + return + } + idx = idx.pushHead(s.pb, x.Name) + defer func() { s.record(idx, x.size, x.count) }() + } else { + // For newly found heap objects, check if all pointers have been scanned by the DWARF searching. + defer func() { + if x.hb.nextPtr(false) != 0 { + // still has pointer, add to the finalMarks + s.finalMarks = append(s.finalMarks, finalMarkParam{idx, x.hb}) + } + }() + } + switch typ := x.RealType.(type) { + case *godwarf.PtrType: + ptrval, err := x.readPointer(x.Addr) + if err != nil { + return + } + if y := s.findObject(Address(ptrval), resolveTypedef(typ.Type), proc.DereferenceMemory(x.mem)); y != nil { + s.findRef(y, idx) + // flatten reference + x.size += y.size + x.count += y.count + } + case *godwarf.ChanType: + ptrval, err := x.readPointer(x.Addr) + if err != nil { + return + } + if y := s.findObject(Address(ptrval), resolveTypedef(typ.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(x.mem)); y != nil { + x.size += y.size + x.count += y.count + + structType, ok := y.RealType.(*godwarf.StructType) + if !ok { + return + } + var zptrval, chanLen uint64 + for _, field := range structType.Field { + switch field.Name { + case "buf": + zptrval, err = y.readPointer(y.Addr.Add(field.ByteOffset)) + if err != nil { + return + } + case "dataqsiz": + chanLen, _ = y.readPointer(y.Addr.Add(field.ByteOffset)) + } + } + if z := s.findObject(Address(zptrval), fakeArrayType(chanLen, typ.ElemType), y.mem); z != nil { + s.findRef(z, idx) + x.size += z.size + x.count += z.count + } + } + case *godwarf.MapType: + ptrval, err := x.readPointer(x.Addr) + if err != nil { + return + } + if y := s.findObject(Address(ptrval), resolveTypedef(typ.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(x.mem)); y != nil { + it, err := s.toMapIterator(y) + if err != nil { + logflags.DebuggerLogger().Errorf("toMapIterator failed: %v", err) + return + } + for s.next(it) { + // find key ref + key := it.key() + key.Name = "$mapkey" + s.findRef(key, idx) + // find val ref + val := it.value() + val.Name = "$mapval" + s.findRef(val, idx) + } + x.size += it.size + x.count += it.count + } + case *godwarf.StringType: + strAddr, strLen, err := readStringInfo(x) + if err != nil { + return + } + if y := s.findObject(Address(strAddr), fakeArrayType(uint64(strLen), &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "byte", ReflectKind: reflect.Uint8}, BitSize: 8, BitOffset: 0}}), proc.DereferenceMemory(x.mem)); y != nil { + s.findRef(y, idx) + x.size += y.size + x.count += y.count + } + case *godwarf.SliceType: + var base, cap_ uint64 + var err error + for _, f := range typ.Field { + switch f.Name { + case "array": + base, err = x.readPointer(x.Addr.Add(f.ByteOffset)) + if err != nil { + return + } + case "cap": + cap_, _ = readUintRaw(x.mem, uint64(int64(x.Addr)+f.ByteOffset), f.Type.Size()) + } + } + if y := s.findObject(Address(base), fakeArrayType(cap_, typ.ElemType), proc.DereferenceMemory(x.mem)); y != nil { + s.findRef(y, idx) + x.size += y.size + x.count += y.count + } + case *godwarf.InterfaceType: + _type, data, _ := s.readInterface(x) + if data == nil { + return + } + ptrval, err := data.readPointer(data.Addr) + if err != nil || ptrval == 0 { + return + } + var ityp godwarf.Type + if _type != nil { + rtyp, kind, err := proc.RuntimeTypeToDIE(_type, uint64(data.Addr), s.mds) + if err == nil { + if kind&kindDirectIface == 0 { + if _, isptr := resolveTypedef(rtyp).(*godwarf.PtrType); !isptr { + rtyp = pointerTo(rtyp, s.bi.Arch) + } + } + if ptrType, isPtr := resolveTypedef(rtyp).(*godwarf.PtrType); isPtr { + ityp = resolveTypedef(ptrType.Type) + } + } + } + if ityp == nil { + ityp = new(godwarf.VoidType) + } + if y := s.findObject(Address(ptrval), ityp, proc.DereferenceMemory(x.mem)); y != nil { + s.findRef(y, idx) + x.size += y.size + x.count += y.count + } + case *godwarf.StructType: + typ = s.specialStructTypes(typ) + for _, field := range typ.Field { + fieldAddr := x.Addr.Add(field.ByteOffset) + if !x.isValid(fieldAddr) { + break + } + if isPrimitiveType(field.Type) { + continue + } + y := newReferenceVariable(fieldAddr, field.Name, resolveTypedef(field.Type), x.mem, x.hb) + s.findRef(y, idx) + } + case *godwarf.ArrayType: + eType := resolveTypedef(typ.Type) + if isPrimitiveType(eType) { + return + } + for i := int64(0); i < typ.Count; i++ { + elemAddr := x.Addr.Add(i * eType.Size()) + if !x.isValid(elemAddr) { + break + } + // collapse 10+ elements by default + name := "[10+]" + if i < 10 { + name = "[" + strconv.Itoa(int(i)) + "]" + } + y := newReferenceVariable(elemAddr, name, eType, x.mem, x.hb) + s.findRef(y, idx) + } + case *godwarf.FuncType: + closureAddr, err := x.readPointer(x.Addr) + if err != nil || closureAddr == 0 { + return + } + var cst godwarf.Type + funcAddr, err := readUintRaw(proc.DereferenceMemory(x.mem), closureAddr, int64(s.bi.Arch.PtrSize())) + if err == nil && funcAddr != 0 { + if fn := s.bi.PCToFunc(funcAddr); fn != nil { + // cst := extra(fn, s.bi).closureStructType + cst = &godwarf.StructType{ + Kind: "struct", + } + } + } + if cst == nil { + cst = new(godwarf.VoidType) + } + if closure := s.findObject(Address(closureAddr), cst, proc.DereferenceMemory(x.mem)); closure != nil { + s.findRef(closure, idx) + x.size += closure.size + x.count += closure.count + } + case *finalizePtrType: + if y := s.findObject(x.Addr, new(godwarf.VoidType), x.mem); y != nil { + s.findRef(y, idx) + x.size += y.size + x.count += y.count + } + default: + } +} + +var atomicPointerRegex = regexp.MustCompile(`^sync/atomic\.Pointer\[.*\]$`) + +func (s *ObjRefScope) specialStructTypes(st *godwarf.StructType) *godwarf.StructType { + switch { + case atomicPointerRegex.MatchString(st.StructName): + // v *sync.readOnly + nst := *st + nst.Field = make([]*godwarf.StructField, len(st.Field)) + copy(nst.Field, st.Field) + nf := *nst.Field[2] + nf.Type = nst.Field[0].Type.(*godwarf.ArrayType).Type + nst.Field[2] = &nf + return &nst + } + return st +} + +func isPrimitiveType(typ godwarf.Type) bool { + typ = resolveTypedef(typ) + switch typ.(type) { + case *godwarf.BoolType, *godwarf.FloatType, *godwarf.UintType, + *godwarf.UcharType, *godwarf.CharType, *godwarf.IntType, *godwarf.ComplexType: + return true + } + return false +} + +var loadSingleValue = proc.LoadConfig{} + +// ObjectReference scanning goroutine stack and global vars to search all heap objects they reference, +// and outputs the reference relationship to the filename with pprof format. +func ObjectReference(t *proc.Target, filename string) error { + scope, err := proc.ThreadScope(t, t.CurrentThread()) + if err != nil { + return err + } + + heapScope := &HeapScope{mem: t.Memory(), bi: t.BinInfo(), scope: scope} + err = heapScope.readHeap() + if err != nil { + return err + } + + f, err := os.Create(filename) + if err != nil { + return err + } + + s := &ObjRefScope{ + HeapScope: heapScope, + pb: newProfileBuilder(f), + } + + mds, err := proc.LoadModuleData(t.BinInfo(), t.Memory()) + if err != nil { + return err + } + s.mds = mds + + // Global variables + pvs, _ := scope.PackageVariables(loadSingleValue) + for _, pv := range pvs { + if pv.Addr == 0 { + continue + } + s.findRef(newReferenceVariable(Address(pv.Addr), pv.Name, pv.RealType, t.Memory(), nil), nil) + } + + // Local variables + threadID := t.CurrentThread().ThreadID() + grs, _, _ := proc.GoroutinesInfo(t, 0, 0) + for _, gr := range grs { + s.g = &stack{} + lo, hi := getStack(gr) + s.g.init(Address(lo), Address(hi)) + if gr.Thread != nil { + threadID = gr.Thread.ThreadID() + } + sf, _ := proc.GoroutineStacktrace(t, gr, 1024, 0) + if len(sf) > 0 { + for i := range sf { + ms := myEvalScope{EvalScope: *proc.FrameToScope(t, t.Memory(), gr, threadID, sf[i:]...)} + locals, err := ms.Locals(mds) + if err != nil { + logflags.DebuggerLogger().Errorf("local variables err: %v", err) + continue + } + for _, l := range locals { + if l.Addr == 0 { + continue + } + if l.Name[0] == '&' { + // escaped variables + l.Name = l.Name[1:] + } + l.Name = sf[i].Current.Fn.Name + "." + l.Name + s.findRef(l, nil) + } + } + } + } + s.g = nil + + // Finalizers + for _, fin := range heapScope.finalizers { + // scan object + s.findRef(newReferenceVariable(fin.p, "finalized", new(finalizePtrType), s.mem, nil), nil) + // scan finalizer + s.findRef(newReferenceVariable(fin.fn, "finalizer", new(godwarf.FuncType), s.mem, nil), nil) + } + + for _, param := range s.finalMarks { + s.finalMark(param.idx, param.hb) + } + + s.pb.flush() + log.Printf("successfully output to `%s`\n", filename) + return nil +} diff --git a/pkg/proc/protobuf.go b/pkg/proc/protobuf.go new file mode 100644 index 0000000..db2b9bc --- /dev/null +++ b/pkg/proc/protobuf.go @@ -0,0 +1,364 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Code forked from https://github.com/golang/go/blob/go1.20.14/src/runtime/pprof/protobuf.go +// +// This file has been modified by CloudWeGo authors. All CloudWeGo +// Modifications are Copyright 2024 CloudWeGo Authors. + +package proc + +import ( + "compress/gzip" + "io" +) + +// A protobuf is a simple protocol buffer encoder. +type protobuf struct { + data []byte + tmp [16]byte + nest int +} + +func (b *protobuf) varint(x uint64) { + for x >= 128 { + b.data = append(b.data, byte(x)|0x80) + x >>= 7 + } + b.data = append(b.data, byte(x)) +} + +func (b *protobuf) length(tag, len int) { + b.varint(uint64(tag)<<3 | 2) + b.varint(uint64(len)) +} + +func (b *protobuf) uint64(tag int, x uint64) { + // append varint to b.data + b.varint(uint64(tag) << 3) + b.varint(x) +} + +func (b *protobuf) uint64s(tag int, x []uint64) { + if len(x) > 2 { + // Use packed encoding + n1 := len(b.data) + for _, u := range x { + b.varint(u) + } + n2 := len(b.data) + b.length(tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + return + } + for _, u := range x { + b.uint64(tag, u) + } +} + +func (b *protobuf) uint64Opt(tag int, x uint64) { + if x == 0 { + return + } + b.uint64(tag, x) +} + +func (b *protobuf) int64(tag int, x int64) { + u := uint64(x) + b.uint64(tag, u) +} + +func (b *protobuf) int64Opt(tag int, x int64) { + if x == 0 { + return + } + b.int64(tag, x) +} + +func (b *protobuf) int64s(tag int, x []int64) { + if len(x) > 2 { + // Use packed encoding + n1 := len(b.data) + for _, u := range x { + b.varint(uint64(u)) + } + n2 := len(b.data) + b.length(tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + return + } + for _, u := range x { + b.int64(tag, u) + } +} + +func (b *protobuf) string(tag int, x string) { + b.length(tag, len(x)) + b.data = append(b.data, x...) +} + +func (b *protobuf) strings(tag int, x []string) { + for _, s := range x { + b.string(tag, s) + } +} + +//func (b *protobuf) stringOpt(tag int, x string) { +// if x == "" { +// return +// } +// b.string(tag, x) +//} + +func (b *protobuf) bool(tag int, x bool) { + if x { + b.uint64(tag, 1) + } else { + b.uint64(tag, 0) + } +} + +//func (b *protobuf) boolOpt(tag int, x bool) { +// if !x { +// return +// } +// b.bool(tag, x) +//} + +type msgOffset int + +func (b *protobuf) startMessage() msgOffset { + b.nest++ + return msgOffset(len(b.data)) +} + +func (b *protobuf) endMessage(tag int, start msgOffset) { + n1 := int(start) + n2 := len(b.data) + b.length(tag, n2-n1) + n3 := len(b.data) + copy(b.tmp[:], b.data[n2:n3]) + copy(b.data[n1+(n3-n2):], b.data[n1:n2]) + copy(b.data[n1:], b.tmp[:n3-n2]) + b.nest-- +} + +const ( + // message Profile + tagProfile_SampleType = 1 // repeated ValueType + tagProfile_Sample = 2 // repeated Sample + tagProfile_Mapping = 3 // repeated Mapping + tagProfile_Location = 4 // repeated Location + tagProfile_Function = 5 // repeated Function + tagProfile_StringTable = 6 // repeated string + tagProfile_DropFrames = 7 // int64 (string table index) + tagProfile_KeepFrames = 8 // int64 (string table index) + tagProfile_TimeNanos = 9 // int64 + tagProfile_DurationNanos = 10 // int64 + tagProfile_PeriodType = 11 // ValueType (really optional string???) + tagProfile_Period = 12 // int64 + tagProfile_Comment = 13 // repeated int64 + tagProfile_DefaultSampleType = 14 // int64 + + // message ValueType + tagValueType_Type = 1 // int64 (string table index) + tagValueType_Unit = 2 // int64 (string table index) + + // message Sample + tagSample_Location = 1 // repeated uint64 + tagSample_Value = 2 // repeated int64 + tagSample_Label = 3 // repeated Label + + // message Label + // tagLabel_Key = 1 // int64 (string table index) + // tagLabel_Str = 2 // int64 (string table index) + // tagLabel_Num = 3 // int64 + + // message Mapping + tagMapping_ID = 1 // uint64 + tagMapping_Start = 2 // uint64 + tagMapping_Limit = 3 // uint64 + tagMapping_Offset = 4 // uint64 + tagMapping_Filename = 5 // int64 (string table index) + tagMapping_BuildID = 6 // int64 (string table index) + tagMapping_HasFunctions = 7 // bool + tagMapping_HasFilenames = 8 // bool + tagMapping_HasLineNumbers = 9 // bool + tagMapping_HasInlineFrames = 10 // bool + + // message Location + tagLocation_ID = 1 // uint64 + tagLocation_MappingID = 2 // uint64 + tagLocation_Address = 3 // uint64 + tagLocation_Line = 4 // repeated Line + + // message Line + tagLine_FunctionID = 1 // uint64 + tagLine_Line = 2 // int64 + + // message Function + tagFunction_ID = 1 // uint64 + tagFunction_Name = 2 // int64 (string table index) + tagFunction_SystemName = 3 // int64 (string table index) + tagFunction_Filename = 4 // int64 (string table index) + tagFunction_StartLine = 5 // int64 +) + +// A profileBuilder writes a profile incrementally from a +// stream of profile samples delivered by the runtime. +type profileBuilder struct { + w io.Writer + zw *gzip.Writer + + pb protobuf + strings []string + stringMap map[string]int + + // key: indexes, val: *profileNode + nodes map[string]*profileNode +} + +type profileNode struct { + count int64 + size int64 +} + +// newProfileBuilder returns a new profileBuilder. +// CPU profiling data obtained from the runtime can be added +// by calling b.addCPUData, and then the eventual profile +// can be obtained by calling b.finish. +func newProfileBuilder(w io.Writer) *profileBuilder { + zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) + b := &profileBuilder{ + w: w, + zw: zw, + strings: []string{""}, + stringMap: map[string]int{"": 0}, + nodes: make(map[string]*profileNode), + } + b.pbValueType(tagProfile_SampleType, "inuse_objects", "count") + b.pbValueType(tagProfile_SampleType, "inuse_space", "bytes") + return b +} + +// pbLine encodes a Line message to b.pb. +func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) { + start := b.pb.startMessage() + b.pb.uint64Opt(tagLine_FunctionID, funcID) + b.pb.int64Opt(tagLine_Line, line) + b.pb.endMessage(tag, start) +} + +// pbValueType encodes a ValueType message to b.pb. +func (b *profileBuilder) pbValueType(tag int, typ, unit string) { + start := b.pb.startMessage() + b.pb.int64(tagValueType_Type, b.stringIndex(typ)) + b.pb.int64(tagValueType_Unit, b.stringIndex(unit)) + b.pb.endMessage(tag, start) +} + +// stringIndex adds s to the string table if not already present +// and returns the index of s in the string table. +func (b *profileBuilder) stringIndex(s string) int64 { + id, ok := b.stringMap[s] + if !ok { + id = len(b.strings) + b.strings = append(b.strings, s) + b.stringMap[s] = id + } + return int64(id) +} + +func (b *profileBuilder) addReference(indexes []uint64, count, bytes int64) { + k := uint64s2str(indexes) + var node *profileNode + if node = b.nodes[k]; node == nil { + node = &profileNode{} + b.nodes[k] = node + } + node.count += count + node.size += bytes +} + +func (b *profileBuilder) flushReference() { + for k, node := range b.nodes { + indexes := str2uint64s(k) + start := b.pb.startMessage() + b.pb.int64s(tagSample_Value, []int64{node.count, node.size}) + b.pb.uint64s(tagSample_Location, indexes) + b.pb.endMessage(tagProfile_Sample, start) + } +} + +func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) { + start := b.pb.startMessage() + b.pb.uint64Opt(tagMapping_ID, id) + b.pb.uint64Opt(tagMapping_Start, base) + b.pb.uint64Opt(tagMapping_Limit, limit) + b.pb.uint64Opt(tagMapping_Offset, offset) + b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file)) + b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID)) + b.pb.bool(tagMapping_HasFunctions, hasFuncs) + b.pb.endMessage(tag, start) +} + +func (b *profileBuilder) flush() { + b.flushReference() + for i := uint64(5); i < uint64(len(b.strings)); i++ { + // write location + start := b.pb.startMessage() + b.pb.uint64Opt(tagLocation_ID, i) + b.pbLine(tagLocation_Line, i, 0) + b.pb.endMessage(tagProfile_Location, start) + + // write function + start = b.pb.startMessage() + b.pb.uint64Opt(tagFunction_ID, i) + b.pb.int64Opt(tagFunction_Name, int64(i)) + b.pb.endMessage(tagProfile_Function, start) + } + // just avoid error msg from pprof tool + b.pbMapping(tagProfile_Mapping, uint64(1), uint64(0), uint64(0xff), 0, "-", "", false) + b.pb.strings(tagProfile_StringTable, b.strings) + b.zw.Write(b.pb.data) + b.zw.Close() +} + +type pprofIndex struct { + idx uint64 + prev *pprofIndex + depth int +} + +func (i *pprofIndex) pushHead(pb *profileBuilder, name string) *pprofIndex { + if name == "" { + return i + } + idx := uint64(pb.stringIndex(name)) + pi := &pprofIndex{ + prev: i, + idx: idx, + } + if i == nil { + pi.depth = 0 + } else { + pi.depth = i.depth + 1 + } + return pi +} + +func (i *pprofIndex) indexes() (res []uint64) { + tmp := i + for tmp != nil { + res = append(res, tmp.idx) + tmp = tmp.prev + } + return +} diff --git a/pkg/proc/region.go b/pkg/proc/region.go new file mode 100644 index 0000000..ae2d0d3 --- /dev/null +++ b/pkg/proc/region.go @@ -0,0 +1,317 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Code forked from https://github.com/golang/debug/blob/c35ceaf7/internal/gocore/region.go +// +// This file has been modified by CloudWeGo authors. All CloudWeGo +// Modifications are Copyright 2024 CloudWeGo Authors. + +package proc + +import ( + "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/proc" +) + +func toRegion(v *proc.Variable, bi *proc.BinaryInfo) *region { + return ®ion{ + mem: getVariableMem(v), + bi: bi, + a: Address(v.Addr), + typ: v.RealType, + } +} + +// A region is a piece of the virtual address space of the inferior. +// It has an address and a type. +// Note that it is the type of the thing in the region, +// not the type of the reference to the region. +type region struct { + mem proc.MemoryReadWriter + bi *proc.BinaryInfo + a Address + typ godwarf.Type +} + +// Address returns the address that a region of pointer type points to. +func (r *region) Address() Address { + switch t := r.typ.(type) { + case *godwarf.PtrType: + ptr, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return Address(ptr) + default: + panic("can't ask for the Address of a non-pointer " + t.String()) + } +} + +// Int returns the int value stored in r. +func (r *region) Int() int64 { + switch t := r.typ.(type) { + case *godwarf.IntType: + if t.Size() != int64(r.bi.Arch.PtrSize()) { + panic("not an int: " + t.String()) + } + i, _ := readIntRaw(r.mem, uint64(r.a), t.Size()) + return i + default: + panic("not an int: " + t.String()) + } +} + +// Uintptr returns the uintptr value stored in r. +func (r *region) Uintptr() uint64 { + switch t := r.typ.(type) { + case *godwarf.UintType: + if t.Size() != int64(r.bi.Arch.PtrSize()) { + panic("not an uintptr: " + t.String()) + } + i, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return i + default: + panic("not a uintptr: " + t.String()) + } +} + +// Deref loads from a pointer. r must contain a pointer. +func (r *region) Deref() *region { + switch t := r.typ.(type) { + case *godwarf.PtrType: + ptr, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + re := ®ion{bi: r.bi, a: Address(ptr), typ: resolveTypedef(t.Type)} + re.mem = cacheMemory(r.mem, uint64(re.a), int(re.typ.Size())) + return re + default: + panic("can't deref on non-pointer: " + t.String()) + } +} + +// Uint64 returns the uint64 value stored in r. +// r must have type uint64. +func (r *region) Uint64() uint64 { + switch t := r.typ.(type) { + case *godwarf.UintType: + if t.Size() != 8 { + panic("bad uint64 type " + t.String()) + } + i, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return i + default: + panic("bad uint64 type " + t.String()) + } +} + +// Uint32 returns the uint32 value stored in r. +// r must have type uint32. +func (r *region) Uint32() uint32 { + switch t := r.typ.(type) { + case *godwarf.UintType: + if t.Size() != 4 { + panic("bad uint32 type " + t.String()) + } + i, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return uint32(i) + default: + panic("bad uint32 type " + t.String()) + } +} + +// Int32 returns the int32 value stored in r. +// r must have type int32. +func (r *region) Int32() int32 { + switch t := r.typ.(type) { + case *godwarf.IntType: + if t.Size() != 4 { + panic("bad int32 type " + t.String()) + } + i, _ := readIntRaw(r.mem, uint64(r.a), t.Size()) + return int32(i) + default: + panic("bad int32 type " + t.String()) + } +} + +// Uint16 returns the uint16 value stored in r. +// r must have type uint16. +func (r *region) Uint16() uint16 { + switch t := r.typ.(type) { + case *godwarf.UintType: + if t.Size() != 2 { + panic("bad uint16 type " + t.String()) + } + i, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return uint16(i) + default: + panic("bad uint16 type " + t.String()) + } +} + +// Uint8 returns the uint8 value stored in r. +// r must have type uint8. +func (r *region) Uint8() uint8 { + switch t := r.typ.(type) { + case *godwarf.UintType: + if t.Size() != 1 { + panic("bad uint8 type " + t.String()) + } + i, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return uint8(i) + default: + panic("bad uint8 type " + t.String()) + } +} + +// Bool returns the bool value stored in r. +// r must have type bool. +func (r *region) Bool() bool { + switch t := r.typ.(type) { + case *godwarf.BoolType: + i, _ := readUintRaw(r.mem, uint64(r.a), t.Size()) + return uint8(i) != 0 + default: + panic("bad bool type " + r.typ.String()) + } +} + +// String returns the value of the string stored in r. +func (r *region) String() string { + switch t := r.typ.(type) { + case *godwarf.StringType: + ptrSize := int64(r.bi.Arch.PtrSize()) + p, _ := readUintRaw(r.mem, uint64(r.a), ptrSize) + n, _ := readUintRaw(r.mem, uint64(r.a.Add(ptrSize)), ptrSize) + b := make([]byte, n) + r.mem.ReadMemory(b, p) + return string(b) + default: + panic("bad string type " + t.String()) + } +} + +// SliceIndex indexes a slice (a[n]). r must contain a slice. +// n must be in bounds for the slice. +func (r *region) SliceIndex(n int64) *region { + switch t := r.typ.(type) { + case *godwarf.SliceType: + ptrSize := int64(r.bi.Arch.PtrSize()) + p, _ := readUintRaw(r.mem, uint64(r.a), ptrSize) + re := ®ion{bi: r.bi, a: Address(p).Add(n * t.ElemType.Size()), typ: resolveTypedef(t.ElemType), mem: r.mem} + return re + default: + panic("can't index a non-slice") + } +} + +// SliceLen returns the length of a slice. r must contain a slice. +func (r *region) SliceLen() int64 { + switch r.typ.(type) { + case *godwarf.SliceType: + ptrSize := int64(r.bi.Arch.PtrSize()) + p, _ := readIntRaw(r.mem, uint64(r.a.Add(ptrSize)), ptrSize) + return p + default: + panic("can't len a non-slice") + } +} + +// SliceCap returns the capacity of a slice. r must contain a slice. +func (r *region) SliceCap() int64 { + switch r.typ.(type) { + case *godwarf.SliceType: + ptrSize := int64(r.bi.Arch.PtrSize()) + p, _ := readIntRaw(r.mem, uint64(r.a.Add(ptrSize*2)), ptrSize) + return p + default: + panic("can't cap a non-slice") + } +} + +// Field returns the part of r which contains the field f. +// r must contain a struct, and f must be one of its fields. +func (r *region) Field(fn string) *region { + switch t := r.typ.(type) { + case *godwarf.StructType: + for _, f := range t.Field { + if f.Name == fn { + re := ®ion{bi: r.bi, a: r.a.Add(f.ByteOffset), typ: resolveTypedef(f.Type), mem: r.mem} + return re + } + } + } + panic("can't find field " + r.typ.String() + "." + fn) +} + +func (r *region) HasField(fn string) bool { + switch t := r.typ.(type) { + case *godwarf.StructType: + for _, f := range t.Field { + if f.Name == fn { + return true + } + } + } + return false +} + +func (r *region) Array() *region { + switch t := r.typ.(type) { + case *godwarf.SliceType: + ptrSize := int64(r.bi.Arch.PtrSize()) + p, _ := readUintRaw(r.mem, uint64(r.a), ptrSize) + c, _ := readUintRaw(r.mem, uint64(r.a.Add(ptrSize)), ptrSize) + re := ®ion{bi: r.bi, a: Address(p), typ: fakeArrayType(c, resolveTypedef(t.ElemType))} + re.mem = cacheMemory(r.mem, p, int(re.typ.Size())) + return re + default: + panic("can't deref a non-slice") + } +} + +func (r *region) ArrayLen() int64 { + switch t := r.typ.(type) { + case *godwarf.ArrayType: + return t.Count + default: + panic("can't ArrayLen a non-array") + } +} + +func (r *region) ArrayIndex(i int64, to *region) { + switch t := r.typ.(type) { + case *godwarf.ArrayType: + if i < 0 || i >= t.Count { + panic("array index out of bounds") + } + to.mem = r.mem + to.bi = r.bi + to.a = r.a.Add(i * t.Type.Size()) + to.typ = resolveTypedef(t.Type) + return + default: + panic("can't ArrayLen a non-array") + } +} + +func (r *region) ArrayElemType() godwarf.Type { + switch t := r.typ.(type) { + case *godwarf.ArrayType: + return t.Type + default: + panic("can't ArrayElemType a non-array") + } +} + +func (r *region) IsStruct() bool { + _, ok := r.typ.(*godwarf.StructType) + return ok +} + +func (r *region) IsArray() bool { + _, ok := r.typ.(*godwarf.ArrayType) + return ok +} + +func (r *region) IsUint16() bool { + t, ok := r.typ.(*godwarf.UintType) + return ok && t.Size() == 2 +} diff --git a/pkg/proc/runtime.go b/pkg/proc/runtime.go new file mode 100644 index 0000000..f0631a2 --- /dev/null +++ b/pkg/proc/runtime.go @@ -0,0 +1,64 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "reflect" + "unsafe" +) + +// A spanClass represents the size class and noscan-ness of a span. +type spanClass uint8 + +func (sc spanClass) noscan() bool { + return sc&1 != 0 +} + +func (sc spanClass) sizeclass() int8 { + return int8(sc >> 1) +} + +type ( + TFlag uint8 + NameOff int32 + TypeOff int32 +) + +// Type copied from go/src/internal/abi/type.go. +type Type struct { + Size_ uintptr + PtrBytes uintptr + Hash uint32 + TFlag TFlag + Align_ uint8 + FieldAlign_ uint8 + Kind_ uint8 + Equal func(unsafe.Pointer, unsafe.Pointer) bool + GCData *byte + Str NameOff + PtrToThis TypeOff +} + +var sizeOffset, ptrBytesOffset, gcDataOffset int64 + +func init() { + rtype := reflect.TypeOf(Type{}) + sf, _ := rtype.FieldByName("Size_") + sizeOffset = int64(sf.Offset) + sf, _ = rtype.FieldByName("PtrBytes") + ptrBytesOffset = int64(sf.Offset) + sf, _ = rtype.FieldByName("GCData") + gcDataOffset = int64(sf.Offset) +} diff --git a/pkg/proc/unsafe.go b/pkg/proc/unsafe.go new file mode 100644 index 0000000..b51cbeb --- /dev/null +++ b/pkg/proc/unsafe.go @@ -0,0 +1,116 @@ +// Copyright 2024 CloudWeGo Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proc + +import ( + "debug/dwarf" + "unsafe" + _ "unsafe" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/proc" + "github.com/modern-go/reflect2" +) + +/* + * Although reluctant to do so, the unsafe operations within this file are currently necessary. + * However, I believe in the future, we will consider managing the dependencies on delve in a more appropriate manner. + */ + +var ( + memField reflect2.StructField + stackField reflect2.StructField + stackLoField reflect2.StructField + stackHiField reflect2.StructField + offsetField reflect2.StructField +) + +func init() { + vt := reflect2.TypeOf(proc.Variable{}).(reflect2.StructType) + memField = vt.FieldByName("mem") + + gt := reflect2.TypeOf(proc.G{}).(reflect2.StructType) + stackField = gt.FieldByName("stack") + + st := reflect2.TypeOf(stackField.Get(&proc.G{})).(reflect2.PtrType).Elem().(reflect2.StructType) + stackLoField = st.FieldByName("lo") + stackHiField = st.FieldByName("hi") + + ft := reflect2.TypeOf(proc.Function{}).(reflect2.StructType) + offsetField = ft.FieldByName("offset") +} + +func getVariableMem(v *proc.Variable) proc.MemoryReadWriter { + return *memField.Get(v).(*proc.MemoryReadWriter) +} + +func getStack(g *proc.G) (lo, hi uint64) { + stack := stackField.Get(g) + lo = *stackLoField.Get(stack).(*uint64) + hi = *stackHiField.Get(stack).(*uint64) + return +} + +func getFunctionOffset(f *proc.Function) (offset dwarf.Offset) { + return *offsetField.Get(f).(*dwarf.Offset) +} + +/* +type functionExtra struct { + // closureStructType is the cached struct type for closures for this function + closureStructType *godwarf.StructType + + // rangeParent is set when this function is a range-over-func body closure + // and points to the function that the closure was generated from. + rangeParent *Function + // rangeBodies is the list of range-over-func body closures for this + // function. Only one between rangeParent and rangeBodies should be set at + // any given time. + rangeBodies []*Function +} + +// Not support closure type before go1.23. TODO: support go1.23 +// +//go:linkname extra github.com/go-delve/delve/pkg/proc.(*Function).extra +func extra(f *Function, bi *BinaryInfo) (e *functionExtra) +*/ + +//go:linkname image github.com/go-delve/delve/pkg/proc.(*EvalScope).image +func image(scope *proc.EvalScope) *proc.Image + +//go:linkname getDwarfTree github.com/go-delve/delve/pkg/proc.(*Image).getDwarfTree +func getDwarfTree(image *proc.Image, off dwarf.Offset) (*godwarf.Tree, error) + +//go:linkname findType github.com/go-delve/delve/pkg/proc.(*BinaryInfo).findType +func findType(bi *proc.BinaryInfo, name string) (godwarf.Type, error) + +//go:linkname rangeParentName github.com/go-delve/delve/pkg/proc.(*Function).rangeParentName +func rangeParentName(fn *proc.Function) string + +//go:linkname readVarEntry github.com/go-delve/delve/pkg/proc.readVarEntry +func readVarEntry(entry *godwarf.Tree, image *proc.Image) (name string, typ godwarf.Type, err error) + +//go:linkname newVariable github.com/go-delve/delve/pkg/proc.newVariable +func newVariable(name string, addr uint64, dwarfType godwarf.Type, bi *proc.BinaryInfo, mem proc.MemoryReadWriter) *proc.Variable + +func uint64s2str(us []uint64) string { + p := unsafe.Pointer(unsafe.SliceData(us)) + return unsafe.String((*byte)(p), len(us)*8) +} + +func str2uint64s(s string) []uint64 { + p := unsafe.Pointer(unsafe.StringData(s)) + return unsafe.Slice((*uint64)(p), len(s)/8) +} diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go new file mode 100644 index 0000000..0cbca4e --- /dev/null +++ b/pkg/proc/variables.go @@ -0,0 +1,596 @@ +// Copyright (c) 2014 Derek Parker +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// This file may have been modified by CloudWeGo authors. All CloudWeGo +// Modifications are Copyright 2024 CloudWeGo Authors. + +package proc + +import ( + "encoding/binary" + "errors" + "fmt" + "reflect" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/goversion" + "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc" +) + +const ( + // hashTophashEmptyZero is used by map reading code, indicates an empty cell + hashTophashEmptyZero = 0 // +rtype emptyRest + // hashTophashEmptyOne is used by map reading code, indicates an empty cell in Go 1.12 and later + hashTophashEmptyOne = 1 // +rtype emptyOne + // hashMinTopHashGo111 used by map reading code, indicates minimum value of tophash that isn't empty or evacuated, in Go1.11 + hashMinTopHashGo111 = 4 // +rtype minTopHash + // hashMinTopHashGo112 is used by map reading code, indicates minimum value of tophash that isn't empty or evacuated, in Go1.12 + hashMinTopHashGo112 = 5 // +rtype minTopHash +) + +// The kind field in runtime._type is a reflect.Kind value plus +// some extra flags defined here. +// See equivalent declaration in $GOROOT/src/reflect/type.go +const ( + kindDirectIface = 1 << 5 // +rtype kindDirectIface|internal/abi.KindDirectIface + kindGCProg = 1 << 6 // +rtype kindGCProg|internal/abi.KindGCProg + kindNoPointers = 1 << 7 + kindMask = (1 << 5) - 1 // +rtype kindMask|internal/abi.KindMask +) + +// ReferenceVariable represents a variable. It contains the address, name, +// type and other information parsed from both the Dwarf information +// and the memory of the debugged process. +type ReferenceVariable struct { + Addr Address + Name string + RealType godwarf.Type + mem proc.MemoryReadWriter + + // heap bits for this object + // hb.base equals to Addr, hb.end equals to min(Addr.Add(RealType.Size), heapBase.Add(elemSize)) + hb *heapBits + + // node size + size int64 + // node count + count int64 +} + +func newReferenceVariable(addr Address, name string, typ godwarf.Type, mem proc.MemoryReadWriter, hb *heapBits) *ReferenceVariable { + return &ReferenceVariable{Addr: addr, Name: name, RealType: typ, mem: mem, hb: hb} +} + +func newReferenceVariableWithSizeAndCount(addr Address, name string, typ godwarf.Type, mem proc.MemoryReadWriter, hb *heapBits, size, count int64) *ReferenceVariable { + rv := newReferenceVariable(addr, name, typ, mem, hb) + rv.size, rv.count = size, count + return rv +} + +func (v *ReferenceVariable) readPointer(addr Address) (uint64, error) { + v.hb.resetGCMask(v.Addr) + return readUintRaw(v.mem, uint64(addr), 8) +} + +// To avoid traversing fields/elements that escape the actual valid scope. +// e.g. (*[1 << 16]scase)(unsafe.Pointer(cas0)) in runtime.selectgo. +func (v *ReferenceVariable) isValid(addr Address) bool { + if v.hb == nil { + return true + } + return addr >= v.hb.base && addr < v.hb.end +} + +type mapIterator struct { + bi *proc.BinaryInfo + numbuckets uint64 + oldmask uint64 + buckets *ReferenceVariable + oldbuckets *ReferenceVariable + b *ReferenceVariable + bidx uint64 + + tophashes *ReferenceVariable + keys *ReferenceVariable + values *ReferenceVariable + overflow *ReferenceVariable + + maxNumBuckets uint64 // maximum number of buckets to scan + + idx int64 + + hashTophashEmptyOne uint64 // Go 1.12 and later has two sentinel tophash values for an empty cell, this is the second one (the first one hashTophashEmptyZero, the same as Go 1.11 and earlier) + hashMinTopHash uint64 // minimum value of tophash for a cell that isn't either evacuated or empty + + // for record ref mem + size, count int64 +} + +// Code derived from go/src/runtime/hashmap.go +func (s *ObjRefScope) toMapIterator(hmap *ReferenceVariable) (it *mapIterator, err error) { + if hmap.Addr == 0 { + err = errors.New("empty hmap addr") + return + } + maptype, ok := hmap.RealType.(*godwarf.StructType) + if !ok { + err = errors.New("wrong real type for map") + return + } + + it = &mapIterator{bidx: 0, b: nil, idx: 0, bi: s.bi, size: hmap.size, count: hmap.count} + + for _, f := range maptype.Field { + switch f.Name { + // case "count": // +rtype -fieldof hmap int + // v.Len, err = readIntRaw(mem, uint64(addr.Add(f.ByteOffset)), ptrSize) + case "B": // +rtype -fieldof hmap uint8 + var b uint64 + b, err = readUintRaw(hmap.mem, uint64(hmap.Addr.Add(f.ByteOffset)), 1) + if err != nil { + return + } + it.numbuckets = 1 << b + it.oldmask = (1 << (b - 1)) - 1 + case "buckets": // +rtype -fieldof hmap unsafe.Pointer + var ptr uint64 + ptr, err = hmap.readPointer(hmap.Addr.Add(f.ByteOffset)) + if err != nil { + return + } + buckets := s.findObject(Address(ptr), resolveTypedef(f.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(hmap.mem)) + if buckets == nil { + buckets = newReferenceVariable(Address(ptr), "", resolveTypedef(f.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(hmap.mem), nil) + } + buckets.Name = "buckets" + it.buckets = buckets + it.size += buckets.size + it.count += buckets.count + case "oldbuckets": // +rtype -fieldof hmap unsafe.Pointer + var ptr uint64 + ptr, err = hmap.readPointer(hmap.Addr.Add(f.ByteOffset)) + if err != nil { + return + } + oldbuckets := s.findObject(Address(ptr), resolveTypedef(f.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(hmap.mem)) + if oldbuckets == nil { + oldbuckets = newReferenceVariable(Address(ptr), "", resolveTypedef(f.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(hmap.mem), nil) + } + oldbuckets.Name = "oldbuckets" + it.oldbuckets = oldbuckets + it.size += oldbuckets.size + it.count += oldbuckets.count + } + } + + if _, ok = it.buckets.RealType.(*godwarf.StructType); !ok { + err = errMapBucketsNotStruct + return + } + if _, ok = it.oldbuckets.RealType.(*godwarf.StructType); !ok { + err = errMapBucketsNotStruct + return + } + + it.hashTophashEmptyOne = hashTophashEmptyZero + it.hashMinTopHash = hashMinTopHashGo111 + if producer := s.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 12) { + it.hashTophashEmptyOne = hashTophashEmptyOne + it.hashMinTopHash = hashMinTopHashGo112 + } + return +} + +var ( + errMapBucketContentsNotArray = errors.New("malformed map type: keys, values or tophash of a bucket is not an array") + errMapBucketContentsInconsistentLen = errors.New("malformed map type: inconsistent array length in bucket") + errMapBucketsNotStruct = errors.New("malformed map type: buckets, oldbuckets or overflow field not a struct") +) + +func (s *ObjRefScope) nextBucket(it *mapIterator) bool { + if it.overflow != nil && it.overflow.Addr > 0 { + it.b = it.overflow + } else { + it.b = nil + + if it.maxNumBuckets > 0 && it.bidx >= it.maxNumBuckets { + return false + } + + for it.bidx < it.numbuckets { + it.b = it.buckets.clone() + it.b.Addr = it.b.Addr.Add(it.buckets.RealType.Size() * int64(it.bidx)) + + if it.oldbuckets.Addr <= 0 { + break + } + + // if oldbuckets is not nil we are iterating through a map that is in + // the middle of a grow. + // if the bucket we are looking at hasn't been filled in we iterate + // instead through its corresponding "oldbucket" (i.e. the bucket the + // elements of this bucket are coming from) but only if this is the first + // of the two buckets being created from the same oldbucket (otherwise we + // would print some keys twice) + + oldbidx := it.bidx & it.oldmask + oldb := it.oldbuckets.clone() + oldb.Addr = oldb.Addr.Add(it.oldbuckets.RealType.Size() * int64(oldbidx)) + + if it.mapEvacuated(oldb) { + break + } + + if oldbidx == it.bidx { + it.b = oldb + break + } + + // oldbucket origin for current bucket has not been evacuated but we have already + // iterated over it so we should just skip it + it.b = nil + it.bidx++ + } + + if it.b == nil { + return false + } + it.bidx++ + } + + if it.b.Addr <= 0 { + return false + } + + it.tophashes = nil + it.keys = nil + it.values = nil + it.overflow = nil + + for _, f := range it.b.RealType.(*godwarf.StructType).Field { + field := newReferenceVariable(it.b.Addr.Add(f.ByteOffset), f.Name, resolveTypedef(f.Type), it.b.mem, it.b.hb) + switch f.Name { + case "tophash": // +rtype -fieldof bmap [8]uint8 + it.tophashes = field + case "keys": + it.keys = field + case "values": + it.values = field + case "overflow": + ptr, err := it.b.readPointer(field.Addr) + if err != nil { + logflags.DebuggerLogger().Errorf("could not load overflow variable: %v", err) + return false + } + if it.overflow = s.findObject(Address(ptr), field.RealType.(*godwarf.PtrType).Type, proc.DereferenceMemory(it.b.mem)); it.overflow != nil { + it.count += it.overflow.count + it.size += it.overflow.size + } + } + } + + // sanity checks + if it.tophashes == nil || it.keys == nil || it.values == nil { + logflags.DebuggerLogger().Errorf("malformed map type") + return false + } + + tophashesType, ok1 := it.tophashes.RealType.(*godwarf.ArrayType) + keysType, ok2 := it.keys.RealType.(*godwarf.ArrayType) + valuesType, ok3 := it.values.RealType.(*godwarf.ArrayType) + if !ok1 || !ok2 || !ok3 { + logflags.DebuggerLogger().Errorf("%v", errMapBucketContentsNotArray) + return false + } + + if tophashesType.Count != keysType.Count { + logflags.DebuggerLogger().Errorf("%v", errMapBucketContentsInconsistentLen) + return false + } + + if valuesType.Type.Size() > 0 && tophashesType.Count != valuesType.Count { + // if the type of the value is zero-sized (i.e. struct{}) then the values + // array's length is zero. + logflags.DebuggerLogger().Errorf("%v", errMapBucketContentsInconsistentLen) + return false + } + + if it.overflow != nil { + if _, ok := it.overflow.RealType.(*godwarf.StructType); !ok { + logflags.DebuggerLogger().Errorf("%v", errMapBucketsNotStruct) + return false + } + } + + return true +} + +func (s *ObjRefScope) next(it *mapIterator) bool { + for { + if it.b == nil { + r := s.nextBucket(it) + if !r { + return false + } + it.idx = 0 + } + if tophashesType, _ := it.tophashes.RealType.(*godwarf.ArrayType); it.idx >= tophashesType.Count { + r := s.nextBucket(it) + if !r { + return false + } + it.idx = 0 + } + tophash := it.tophashes.clone() + tophash.RealType = tophash.RealType.(*godwarf.ArrayType).Type + tophash.Name = fmt.Sprintf("[%d]", int(it.idx)) + tophash.Addr = tophash.Addr.Add(tophash.RealType.Size() * it.idx) + + h, err := readUintRaw(tophash.mem, uint64(tophash.Addr), 1) + if err != nil { + logflags.DebuggerLogger().Errorf("unreadable tophash: %v", err) + return false + } + it.idx++ + if h != hashTophashEmptyZero && h != it.hashTophashEmptyOne { + return true + } + } +} + +func (it *mapIterator) key() *ReferenceVariable { + k := it.keys.clone() + k.RealType = resolveTypedef(k.RealType.(*godwarf.ArrayType).Type) + k.Addr = k.Addr.Add(k.RealType.Size() * (it.idx - 1)) + // limit heap bits to a single key + k.hb = newHeapBits(k.Addr, k.Addr.Add(k.RealType.Size()), k.hb.sp) + return k +} + +func (it *mapIterator) value() *ReferenceVariable { + v := it.values.clone() + v.RealType = resolveTypedef(v.RealType.(*godwarf.ArrayType).Type) + v.Addr = v.Addr.Add(v.RealType.Size() * (it.idx - 1)) + // limit heap bits to a single value + v.hb = newHeapBits(v.Addr, v.Addr.Add(v.RealType.Size()), v.hb.sp) + return v +} + +func (it *mapIterator) mapEvacuated(b *ReferenceVariable) bool { + if b.Addr == 0 { + return true + } + for _, f := range b.RealType.(*godwarf.StructType).Field { + if f.Name != "tophash" { + continue + } + tophash0, err := readUintRaw(b.mem, uint64(b.Addr.Add(f.ByteOffset)), 1) + if err != nil { + return true + } + // TODO: this needs to be > hashTophashEmptyOne for go >= 1.12 + return tophash0 > it.hashTophashEmptyOne && tophash0 < it.hashMinTopHash + } + return true +} + +func (s *ObjRefScope) readInterface(v *ReferenceVariable) (_type *proc.Variable, data *ReferenceVariable, isnil bool) { + // An interface variable is implemented either by a runtime.iface + // struct or a runtime.eface struct. The difference being that empty + // interfaces (i.e. "interface {}") are represented by runtime.eface + // and non-empty interfaces by runtime.iface. + // + // For both runtime.ifaces and runtime.efaces the data is stored in v.data + // + // The concrete type however is stored in v.tab._type for non-empty + // interfaces and in v._type for empty interfaces. + // + // For nil empty interface variables _type will be nil, for nil + // non-empty interface variables tab will be nil + // + // In either case the _type field is a pointer to a runtime._type struct. + // + // The following code works for both runtime.iface and runtime.eface. + + ityp := resolveTypedef(&v.RealType.(*godwarf.InterfaceType).TypedefType).(*godwarf.StructType) + + // +rtype -field iface.tab *itab|*internal/abi.ITab + // +rtype -field iface.data unsafe.Pointer + // +rtype -field eface._type *_type|*internal/abi.Type + // +rtype -field eface.data unsafe.Pointer + + for _, f := range ityp.Field { + switch f.Name { + case "tab": // for runtime.iface + ptr, err := readUintRaw(v.mem, uint64(v.Addr.Add(f.ByteOffset)), int64(s.bi.Arch.PtrSize())) + if err != nil { + logflags.DebuggerLogger().Errorf("read tab err: %v", err) + continue + } + // +rtype *itab|*internal/abi.ITab + tab := newReferenceVariable(Address(ptr), "", resolveTypedef(f.Type.(*godwarf.PtrType).Type), proc.DereferenceMemory(v.mem), nil) + isnil = tab.Addr == 0 + if !isnil { + for _, tf := range tab.RealType.(*godwarf.StructType).Field { + switch tf.Name { + case "Type": + // +rtype *internal/abi.Type + _type = newVariable("", uint64(tab.Addr.Add(tf.ByteOffset)), tf.Type, s.bi, tab.mem) + case "_type": + // +rtype *_type|*internal/abi.Type + _type = newVariable("", uint64(tab.Addr.Add(tf.ByteOffset)), tf.Type, s.bi, tab.mem) + } + } + if _type == nil { + logflags.DebuggerLogger().Errorf("invalid interface type") + return + } + } + case "_type": // for runtime.eface + _type = newVariable("", uint64(v.Addr.Add(f.ByteOffset)), f.Type, s.bi, v.mem) + ptr, err := readUintRaw(v.mem, _type.Addr, int64(s.bi.Arch.PtrSize())) + if err != nil { + logflags.DebuggerLogger().Errorf("read tab err: %v", err) + continue + } + isnil = ptr == 0 + case "data": + data = newReferenceVariable(v.Addr.Add(f.ByteOffset), "", f.Type, v.mem, v.hb) + } + } + return +} + +func (v *ReferenceVariable) clone() *ReferenceVariable { + r := *v + return &r +} + +// for special treatment to finalize pointers +type finalizePtrType struct { + godwarf.Type +} + +func readIntRaw(mem proc.MemoryReadWriter, addr uint64, size int64) (int64, error) { + var n int64 + + val := make([]byte, int(size)) + _, err := mem.ReadMemory(val, addr) + if err != nil { + return 0, err + } + + switch size { + case 1: + n = int64(int8(val[0])) + case 2: + n = int64(int16(binary.LittleEndian.Uint16(val))) + case 4: + n = int64(int32(binary.LittleEndian.Uint32(val))) + case 8: + n = int64(binary.LittleEndian.Uint64(val)) + } + + return n, nil +} + +func readUintRaw(mem proc.MemoryReadWriter, addr uint64, size int64) (uint64, error) { + var n uint64 + + val := make([]byte, int(size)) + _, err := mem.ReadMemory(val, addr) + if err != nil { + return 0, err + } + + switch size { + case 1: + n = uint64(val[0]) + case 2: + n = uint64(binary.LittleEndian.Uint16(val)) + case 4: + n = uint64(binary.LittleEndian.Uint32(val)) + case 8: + n = binary.LittleEndian.Uint64(val) + } + + return n, nil +} + +func readUint64Array(mem proc.MemoryReadWriter, addr uint64, res []uint64) (err error) { + val := make([]byte, len(res)*8) + _, err = mem.ReadMemory(val, addr) + if err != nil { + return + } + for i := 0; i < len(res); i++ { + res[i] = binary.LittleEndian.Uint64(val[i*8 : (i+1)*8]) + } + return +} + +func readStringInfo(str *ReferenceVariable) (uint64, int64, error) { + // string data structure is always two ptrs in size. Addr, followed by len + // http://research.swtch.com/godata + + var strlen int64 + var outaddr uint64 + var err error + + for _, field := range str.RealType.(*godwarf.StringType).StructType.Field { + switch field.Name { + case "len": + strlen, err = readIntRaw(str.mem, uint64(str.Addr.Add(field.ByteOffset)), 8) + if err != nil { + return 0, 0, fmt.Errorf("could not read string len %s", err) + } + if strlen < 0 { + return 0, 0, fmt.Errorf("invalid length: %d", strlen) + } + case "str": + outaddr, err = str.readPointer(str.Addr.Add(field.ByteOffset)) + if err != nil { + return 0, 0, fmt.Errorf("could not read string pointer %s", err) + } + if outaddr == 0 { + return 0, 0, nil + } + } + } + + return outaddr, strlen, nil +} + +// alignAddr rounds up addr to a multiple of align. Align must be a power of 2. +func alignAddr(addr, align int64) int64 { + return (addr + align - 1) &^ (align - 1) +} + +func fakeArrayType(n uint64, fieldType godwarf.Type) godwarf.Type { + stride := alignAddr(fieldType.Common().ByteSize, fieldType.Align()) + return &godwarf.ArrayType{ + CommonType: godwarf.CommonType{ + ReflectKind: reflect.Array, + ByteSize: int64(n) * stride, + Name: fmt.Sprintf("[%d]%s", n, fieldType.String()), + }, + Type: fieldType, + StrideBitSize: stride * 8, + Count: int64(n), + } +} + +func pointerTo(typ godwarf.Type, arch *proc.Arch) godwarf.Type { + return &godwarf.PtrType{ + CommonType: godwarf.CommonType{ + ByteSize: int64(arch.PtrSize()), + Name: "*" + typ.Common().Name, + ReflectKind: reflect.Ptr, + Offset: 0, + }, + Type: typ, + } +} + +func resolveTypedef(typ godwarf.Type) godwarf.Type { + for { + switch tt := typ.(type) { + case *godwarf.TypedefType: + typ = tt.Type + case *godwarf.QualType: + typ = tt.Type + default: + return typ + } + } +} diff --git a/profile/README.md b/profile/README.md deleted file mode 100644 index 2127160..0000000 --- a/profile/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Hi there πŸ‘‹ - -πŸ™‹β€β™€οΈ A short introduction - CloudWeGo is an open-source middleware set launched by ByteDance that can be used to quickly build enterprise-class cloud native architectures. The common characteristics of CloudWeGo projects are high performance, high scalability, high reliability and focusing on microservices communication and governance. - -🌈 Community Membership - the [Responsibilities and Requirements](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md) of contributor roles in CloudWeGo. - -πŸ‘©β€πŸ’» Useful resources - [Portal](https://www.cloudwego.io/), [Community](https://www.cloudwego.io/zh/community/), [Blogs](https://www.cloudwego.io/zh/blog/), [Use Cases](https://www.cloudwego.io/zh/cooperation/) - -🍿 Security - [Vulnerability Reporting](https://www.cloudwego.io/zh/security/vulnerability-reporting/), [Safety Bulletin](https://www.cloudwego.io/zh/security/safety-bulletin/) - -🌲 Ecosystem - [Kitex-contrib](https://github.com/kitex-contrib), [Hertz-contrib](https://github.com/hertz-contrib), [Volo-rs](https://github.com/volo-rs) - -🎊 Example - [kitex-example](https://github.com/cloudwego/kitex-examples), [hertz-example](https://github.com/cloudwego/hertz-examples), [biz-demo](https://github.com/cloudwego/biz-demo), [netpoll-example](https://github.com/cloudwego/netpoll-examples) diff --git a/testdata/allocheader/main.go b/testdata/allocheader/main.go new file mode 100644 index 0000000..7f6d4c5 --- /dev/null +++ b/testdata/allocheader/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "time" +) + +type PtrStruct struct { + a [3]int64 + c *int32 +} + +type BigElem struct { + Ptrs [512]PtrStruct + X uint64 +} + +var ( + noscan = make([]byte, 1024) + noHeader = make([]PtrStruct, 512/32) + withHeader = make([]PtrStruct, 512) + bigElem *BigElem + bigElem1 BigElem + bigElem2 = &BigElem{} + large = make([]PtrStruct, 512*1024) +) + +func main() { + c := int32(123) + bigElem = &BigElem{} + bigElem.Ptrs[0].c = &c + time.Sleep(100 * time.Second) +} diff --git a/testdata/alltypes/main.go b/testdata/alltypes/main.go new file mode 100644 index 0000000..2336c8a --- /dev/null +++ b/testdata/alltypes/main.go @@ -0,0 +1,220 @@ +package main + +import ( + "context" + "encoding/json" + "runtime" + "time" + "unsafe" +) + +type InnerMessage struct { + msgs []string +} + +type MyChan struct { + cchan chan *InnerMessage +} + +type SubRequest struct { + E map[string]string + F map[int64]*MyChan +} + +type Request struct { + A *int64 + B *string + C []string + D *SubRequest + X []*SubRequest +} + +func (*Request) String() string { + return "" +} + +type ReqE interface { + String() string +} + +var ( + globalReq = &Request{} + globalCC = make([]string, 1024) +) + +//go:noinline +func escape(req *Request, str interface{}, reqI interface{}, reqE ReqE, bbbb *[2112313131]Request) { + _, _ = json.Marshal(req) + _, _ = json.Marshal(str) + _, _ = json.Marshal(reqI) + _, _ = json.Marshal(reqE) + println(bbbb) +} + +type Pointer[T any] struct { + // Mention *T in a field to disallow conversion between Pointer types. + // See go.dev/issue/56603 for more details. + // Use *T, not T, to avoid spurious recursive type definition errors. + xx [0]*T + + v unsafe.Pointer +} + +// SliceByteToString converts []byte to string without copy. +// DO NOT USE unless you know what you're doing. +func SliceByteToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func genericString() string { + ss := make([]byte, 1024) + ss = ss[100:200] + return SliceByteToString(ss) +} + +func finalizing() { + toFin := [1000]*int64{} + toFin2 := [1000]*int64{} + runtime.SetFinalizer(&toFin, func(*[1000]*int64) { + println(toFin2[0]) + }) +} + +func incall(a *int64, b *string) (res *Request) { + globalReq.C = []string{genericString(), genericString(), genericString()} + req := &Request{ + A: a, + B: b, + C: []string{genericString(), genericString(), genericString()}, + D: &SubRequest{ + E: map[string]string{ + genericString(): genericString(), + }, + F: map[int64]*MyChan{ + 23131: { + cchan: make(chan *InnerMessage, 100), + }, + }, + }, + X: []*SubRequest{ + { + E: map[string]string{ + genericString(): genericString(), + }, + F: map[int64]*MyChan{ + 23131: { + cchan: make(chan *InnerMessage, 100), + }, + }, + }, + }, + } + req.D.F[23131].cchan <- &InnerMessage{ + msgs: []string{genericString(), genericString(), genericString()}, + } + req.X[0].F[23131].cchan <- &InnerMessage{ + msgs: []string{genericString(), genericString(), genericString()}, + } + + reqq := &req.C + + reqqq := &Request{ + A: a, + B: b, + C: []string{genericString(), genericString(), genericString()}, + D: &SubRequest{ + E: map[string]string{ + genericString(): genericString(), + }, + F: map[int64]*MyChan{ + 23131: { + cchan: make(chan *InnerMessage, 100), + }, + }, + }, + X: []*SubRequest{ + { + E: map[string]string{ + genericString(): genericString(), + }, + F: map[int64]*MyChan{ + 23131: { + cchan: make(chan *InnerMessage, 100), + }, + }, + }, + }, + } + req.D.F[23131].cchan <- &InnerMessage{ + msgs: []string{genericString(), genericString(), genericString()}, + } + req.X[0].F[23131].cchan <- &InnerMessage{ + msgs: []string{genericString(), genericString(), genericString()}, + } + + ireq := &Request{ + A: a, + } + + var reqI interface{} = &Request{ + A: a, + } + var reqE ReqE = &Request{ + A: a, + } + + ss := make([]byte, 1024) + + sss := string(ss) + + str := func() *string { + return &sss + } + + println(req.X[0].E[genericString()]) + + println((*reqq)[0]) + + next := func() { + println(a) + println(b) + } + ctx := context.Background() + nnext := func() { + next() + println(ctx.Err()) + } + + // test g stack range + bbbb := (*[2112313131]Request)(unsafe.Pointer(&aaa)) + + finalizing() + + time.Sleep(100 * time.Second) + + go func() { nnext() }() + + _ = reqqq + _ = bbbb + + runtime.KeepAlive(req) + runtime.KeepAlive(ireq) + escape(req, str, reqI, reqE, bbbb) + + _ = reqI + + res = &Request{} + return +} + +var ( + // test bss range + aaa int + bbb = (*[2112313131]Request)(unsafe.Pointer(&aaa)) +) + +func main() { + a := int64(12313) + b := genericString() + incall(&a, &b) +}