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/README.md b/README.md index a46ae92..6eb89da 100644 --- a/README.md +++ b/README.md @@ -1 +1,40 @@ -# .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) + +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 +``` + +## 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` +``` + +## 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..98f4a3d --- /dev/null +++ b/pkg/proc/eval.go @@ -0,0 +1,276 @@ +// 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 localsFlags uint8 + +const ( + // If localsTrustArgOrder is set function arguments that don't have an + // address will have one assigned by looking at their position in the argument + // list. + localsTrustArgOrder localsFlags = 1 << iota + + // If localsNoDeclLineCheck the declaration line isn't checked at + // all to determine if the variable is in scope. + localsNoDeclLineCheck +) + +type myEvalScope struct { + EvalScope + + dictAddr uint64 // dictionary address for instantiated generic functions + + enclosingRangeScopes []*EvalScope + rangeFrames []Stackframe +} + +func (scope *myEvalScope) Locals(mds []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 []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 *BinaryInfo, image *Image, regs op.DwarfRegisters, mem MemoryReadWriter, entry *godwarf.Tree, dictAddr uint64, mds []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, _, err := bi.Location(entry, dwarf.AttrLocation, regs.PC(), regs, mem) + uaddr := uint64(addr) + if pieces != nil { + cmem, _ := 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 *BinaryInfo, mem MemoryReadWriter, t godwarf.Type, dictAddr uint64, mds []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 := RuntimeTypeToDIE(_type, 0, mds) + if err != nil { + return ptyp.TypedefType.Type, err + } + + return typ, nil +} + +func runtimeTypeTypename(bi *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 int, j int) bool { + return v.depths[i] < v.depths[j] +} + +func (v *variablesByDepthAndDeclLine) Swap(i int, 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..9b4e67f --- /dev/null +++ b/pkg/proc/heap.go @@ -0,0 +1,592 @@ +// 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 []ModuleData + + mem MemoryReadWriter + bi *BinaryInfo + scope *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 Address, 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)) + return +} + +// 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..03953eb --- /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 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 MemoryReadWriter, addr uint64, size int) 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..d038e36 --- /dev/null +++ b/pkg/proc/objects.go @@ -0,0 +1,475 @@ +// 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/logflags" + . "github.com/go-delve/delve/pkg/proc" + + "github.com/go-delve/delve/pkg/dwarf/godwarf" +) + +const maxRefDepth = 256 + +type ObjRefScope struct { + *HeapScope + + pb *profileBuilder + + // maybe nil + g *stack +} + +func (s *ObjRefScope) findObject(addr Address, typ godwarf.Type, mem 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 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), 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), 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), 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}}), 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), 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 := 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, 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(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, 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: + } + return +} + +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)) + for j := range st.Field { + nst.Field[j] = st.Field[j] + } + 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 = 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 *Target, filename string) error { + scope, err := 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 := 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, _, _ := 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, _ := GoroutineStacktrace(t, gr, 1024, 0) + if len(sf) > 0 { + for i := range sf { + ms := myEvalScope{EvalScope: *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..78f802f --- /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 int, 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 | 0) + 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..e66a66f --- /dev/null +++ b/pkg/proc/region.go @@ -0,0 +1,322 @@ +// 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 *Variable, bi *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 MemoryReadWriter + bi *BinaryInfo + a Address + typ godwarf.Type +} + +func (r *region) clone() *region { + nr := *r + return &nr +} + +// 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..4b6bfa0 --- /dev/null +++ b/pkg/proc/unsafe.go @@ -0,0 +1,123 @@ +// 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" + "reflect" + "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(Variable{}).(reflect2.StructType) + memField = vt.FieldByName("mem") + + gt := reflect2.TypeOf(G{}).(reflect2.StructType) + stackField = gt.FieldByName("stack") + + st := reflect2.TypeOf(stackField.Get(&G{})).(reflect2.PtrType).Elem().(reflect2.StructType) + stackLoField = st.FieldByName("lo") + stackHiField = st.FieldByName("hi") + + ft := reflect2.TypeOf(Function{}).(reflect2.StructType) + offsetField = ft.FieldByName("offset") +} + +func getVariableMem(v *Variable) MemoryReadWriter { + return *memField.Get(v).(*MemoryReadWriter) +} + +func getStack(g *G) (lo, hi uint64) { + stack := stackField.Get(g) + lo = *stackLoField.Get(stack).(*uint64) + hi = *stackHiField.Get(stack).(*uint64) + return +} + +func getFunctionOffset(f *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 *EvalScope) *Image + +//go:linkname getDwarfTree github.com/go-delve/delve/pkg/proc.(*Image).getDwarfTree +func getDwarfTree(image *Image, off dwarf.Offset) (*godwarf.Tree, error) + +//go:linkname findType github.com/go-delve/delve/pkg/proc.(*BinaryInfo).findType +func findType(bi *BinaryInfo, name string) (godwarf.Type, error) + +//go:linkname rangeParentName github.com/go-delve/delve/pkg/proc.(*Function).rangeParentName +func rangeParentName(fn *Function) string + +//go:linkname readVarEntry github.com/go-delve/delve/pkg/proc.readVarEntry +func readVarEntry(entry *godwarf.Tree, image *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 *BinaryInfo, mem MemoryReadWriter) *Variable + +func uint64s2str(us []uint64) (s string) { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&us)).Data) + hdr := (*reflect.StringHeader)(unsafe.Pointer(&s)) + hdr.Data = uintptr(p) + hdr.Len = len(us) * 8 + return +} + +func str2uint64s(s string) (us []uint64) { + p := unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&s)).Data) + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&us)) + hdr.Data = uintptr(p) + hdr.Len, hdr.Cap = len(s)/8, len(s)/8 + return +} diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go new file mode 100644 index 0000000..113b656 --- /dev/null +++ b/pkg/proc/variables.go @@ -0,0 +1,618 @@ +// 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" + "strconv" + + "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 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 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 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 *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), DereferenceMemory(hmap.mem)) + if buckets == nil { + buckets = newReferenceVariable(Address(ptr), "", resolveTypedef(f.Type.(*godwarf.PtrType).Type), 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), DereferenceMemory(hmap.mem)) + if oldbuckets == nil { + oldbuckets = newReferenceVariable(Address(ptr), "", resolveTypedef(f.Type.(*godwarf.PtrType).Type), 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, 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 *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), 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 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 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 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 *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 + } + } +} + +func filterVariables(vars []*Variable, pred func(v *Variable) bool) []*Variable { + r := make([]*Variable, 0, len(vars)) + for i := range vars { + if pred(vars[i]) { + r = append(r, vars[i]) + } + } + return r +} + +func logPtrSize(ptrSize int) int { + switch ptrSize { + case 8: + return 3 + case 4: + return 2 + default: + panic("invalid ptrSize: " + strconv.Itoa(ptrSize)) + } +} 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) +}