├── .clang-format ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.md ├── GOVERNANCE.md ├── LICENCE.md ├── Makefile ├── README.md ├── binding.gyp ├── common.gypi ├── gyp_llnode ├── llnode.gyp ├── package.json ├── scripts ├── otool2segments.py └── readelf2segments.py ├── src ├── llnode.cc ├── llnode.h ├── llscan.cc ├── llscan.h ├── llv8-constants.cc ├── llv8-constants.h ├── llv8-inl.h ├── llv8.cc └── llv8.h └── test ├── common.js ├── fixtures ├── inspect-scenario.js └── stack-scenario.js ├── inspect-test.js ├── scan-test.js └── stack-test.js /.clang-format: -------------------------------------------------------------------------------- 1 | # Defines the Google C++ style for automatic reformatting. 2 | # http://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | BasedOnStyle: Google 4 | MaxEmptyLinesToKeep: 2 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dylib 2 | *.dSYM 3 | tools/ 4 | out/ 5 | lldb/ 6 | build/ 7 | out/ 8 | npm-debug.log 9 | node_modules/ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | dist: trusty 4 | before_install: 5 | - sudo apt-get -qq update 6 | - sudo apt-get install lldb-3.6 lldb-3.6-dev -y 7 | - git clone https://chromium.googlesource.com/external/gyp.git tools/gyp 8 | node_js: 9 | - "4" 10 | - "5" 11 | - "6" 12 | - "7" 13 | branches: 14 | only: 15 | - master 16 | script: make _travis 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution. 2 | Fedor Indutny 3 | Howard Hellyer 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to llnode 2 | 3 | This document will guide you through the contribution process. 4 | 5 | ### Step 1: Fork 6 | 7 | Fork the project [on GitHub](https://github.com/indutny/llnode) and check out 8 | your copy locally. 9 | 10 | ```text 11 | $ git clone git@github.com:username/llnode.git 12 | $ cd llnode 13 | $ git remote add upstream git://github.com/indutny/llnode.git 14 | ``` 15 | 16 | #### Which branch? 17 | 18 | For developing new features and bug fixes, the `master` branch should be pulled 19 | and built upon. 20 | 21 | ### Step 2: Branch 22 | 23 | Create a feature branch and start hacking: 24 | 25 | ```text 26 | $ git checkout -b my-feature-branch -t origin/master 27 | ``` 28 | 29 | ### Step 3: Commit 30 | 31 | Make sure git knows your name and email address: 32 | 33 | ```text 34 | $ git config --global user.name "J. Random User" 35 | $ git config --global user.email "j.random.user@example.com" 36 | ``` 37 | 38 | Writing good commit logs is important. A commit log should describe what 39 | changed and why. Follow these guidelines when writing one: 40 | 41 | 1. The first line should be 50 characters or less and contain a short 42 | description of the change prefixed with the name of the changed 43 | subsystem (e.g. "net: add localAddress and localPort to Socket"). 44 | 2. Keep the second line blank. 45 | 3. Wrap all other lines at 72 columns. 46 | 47 | A good commit log can look something like this: 48 | 49 | ``` 50 | subsystem: explaining the commit in one line 51 | 52 | Body of commit message is a few lines of text, explaining things 53 | in more detail, possibly giving some background about the issue 54 | being fixed, etc. etc. 55 | 56 | The body of the commit message can be several paragraphs, and 57 | please do proper word-wrap and keep columns shorter than about 58 | 72 characters or so. That way `git log` will show things 59 | nicely even when it is indented. 60 | ``` 61 | 62 | The header line should be meaningful; it is what other people see when they 63 | run `git shortlog` or `git log --oneline`. 64 | 65 | Check the output of `git log --oneline files_that_you_changed` to find out 66 | what subsystem (or subsystems) your changes touch. 67 | 68 | If your patch fixes an open issue, you can add a reference to it at the end 69 | of the log. Use the `Fixes:` prefix and the full issue URL. For example: 70 | 71 | ``` 72 | Fixes: https://github.com/indutny/llnode/issues/1337 73 | ``` 74 | 75 | ### Step 4: Rebase 76 | 77 | Use `git rebase` (not `git merge`) to sync your work from time to time. 78 | 79 | ```text 80 | $ git fetch upstream 81 | $ git rebase upstream/master 82 | ``` 83 | 84 | 85 | ### Step 5: Test 86 | 87 | Bug fixes and features **should come with tests**. Add your tests in the 88 | `test/parallel/` directory. For guidance on how to write a test for the llnode 89 | project, see this [guide](./doc/guides/writing_tests.md). Looking at other tests 90 | to see how they should be structured can also help. 91 | 92 | ```text 93 | $ npm install && npm test 94 | ``` 95 | 96 | Make sure the linter is happy and that all tests pass. Please, do not submit 97 | patches that fail either check. 98 | 99 | ### Step 6: Push 100 | 101 | ```text 102 | $ git push origin my-feature-branch 103 | ``` 104 | 105 | Go to https://github.com/yourusername/llnode and select your feature branch. 106 | Click the 'Pull Request' button and fill out the form. 107 | 108 | Pull requests are usually reviewed within a few days. If there are comments 109 | to address, apply your changes in a separate commit and push that to your 110 | feature branch. Post a comment in the pull request afterwards; GitHub does 111 | not send out notifications when you add commits. 112 | 113 | ## Code of Conduct 114 | 115 | The [Node.js Code of Conduct][] applies to this repo. 116 | 117 | [Node.js Code of Conduct]: https://github.com/nodejs/node/blob/master/CODE_OF_CONDUCT.md 118 | 119 | ## Code Contributions 120 | 121 | The llnode project falls under the governance of the post-mortem 122 | working group which is documented in: 123 | https://github.com/nodejs/post-mortem/blob/master/GOVERNANCE.md 124 | 125 | ## Developer's Certificate of Origin 1.1 126 | 127 | By making a contribution to this project, I certify that: 128 | 129 | * (a) The contribution was created in whole or in part by me and I 130 | have the right to submit it under the open source license 131 | indicated in the file; or 132 | 133 | * (b) The contribution is based upon previous work that, to the best 134 | of my knowledge, is covered under an appropriate open source 135 | license and I have the right under that license to submit that 136 | work with modifications, whether created in whole or in part 137 | by me, under the same open source license (unless I am 138 | permitted to submit under a different license), as indicated 139 | in the file; or 140 | 141 | * (c) The contribution was provided directly to me by some other 142 | person who certified (a), (b) or (c) and I have not modified 143 | it. 144 | 145 | * (d) I understand and agree that this project and the contribution 146 | are public and that a record of the contribution (including all 147 | personal information I submit with it, including my sign-off) is 148 | maintained indefinitely and may be redistributed consistent with 149 | this project or the open source license(s) involved. 150 | -------------------------------------------------------------------------------- /GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # llnode Project Governance 2 | 3 | The llnode project falls under the governance of the post-mortem 4 | working group which is documented in: 5 | https://github.com/nodejs/post-mortem/blob/master/GOVERNANCE.md. 6 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2016 nodereport contributors 5 | -------------------------------------------------- 6 | 7 | *nodereport contributors listed at * 8 | 9 | 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: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | 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 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "Please take a look at README.md" 3 | 4 | install-osx: 5 | mkdir -p ~/Library/Application\ Support/LLDB/PlugIns/ 6 | cp -rf ./out/Release/llnode.dylib \ 7 | ~/Library/Application\ Support/LLDB/PlugIns/ 8 | 9 | uninstall-osx: 10 | rm ~/Library/Application\ Support/LLDB/PlugIns/llnode.dylib 11 | 12 | install-linux: 13 | mkdir -p /usr/lib/lldb 14 | cp -rf ./out/Release/lib.target/llnode.so /usr/lib/lldb/ 15 | 16 | uninstall-linux: 17 | rm /usr/lib/lldb/llnode.so 18 | 19 | format: 20 | clang-format -i src/* 21 | 22 | _travis: 23 | ./gyp_llnode -Dlldb_dir=/usr/lib/llvm-3.6/ -f make 24 | make -C out/ 25 | TEST_LLDB_BINARY=`which lldb-3.6` npm test 26 | 27 | .PHONY: all 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # llnode 2 | 3 | [![Build Status](https://secure.travis-ci.org/indutny/llnode.png)](http://travis-ci.org/indutny/llnode) 4 | 5 | Node.js v4.x-v6.x C++ plugin for [LLDB](http://lldb.llvm.org) - a next generation, high-performance debugger. 6 | 7 | ## Demo 8 | 9 | https://asciinema.org/a/29589 10 | 11 | ## Build instructions 12 | 13 | ### OS X 14 | 15 | Easy: 16 | ```bash 17 | brew install llnode 18 | ``` 19 | 20 | Harder: 21 | ```bash 22 | # Clone this repo 23 | git clone https://github.com/nodejs/llnode.git && cd llnode 24 | 25 | # Check out source code of the LLDB that is compatible with OS X's default 26 | # lldb 27 | git clone --depth=1 -b release_38 https://github.com/llvm-mirror/lldb.git lldb 28 | 29 | # Initialize GYP 30 | git clone https://chromium.googlesource.com/external/gyp.git tools/gyp 31 | 32 | # Configure 33 | ./gyp_lldb 34 | 35 | # Build 36 | make -C out/ -j9 37 | 38 | # Install 39 | make install-osx 40 | ``` 41 | 42 | ### Linux 43 | 44 | ```bash 45 | # Clone this repo 46 | git clone https://github.com/nodejs/llnode.git && cd llnode 47 | 48 | # Install lldb and headers 49 | sudo apt-get install lldb-3.8 lldb-3.8-dev 50 | 51 | # Initialize GYP 52 | git clone https://chromium.googlesource.com/external/gyp.git tools/gyp 53 | 54 | # Configure 55 | ./gyp_llnode -Dlldb_dir=/usr/lib/llvm-3.8/ 56 | 57 | # Build 58 | make -C out/ -j9 59 | 60 | # Install 61 | sudo make install-linux 62 | ``` 63 | 64 | ### Usage 65 | 66 | ``` 67 | lldb 68 | (lldb) v8 help 69 | Node.js helpers 70 | 71 | Syntax: v8 72 | 73 | The following subcommands are supported: 74 | 75 | bt -- Show a backtrace with node.js JavaScript functions and their args. An optional argument is accepted; if 76 | that argument is a number, it specifies the number of frames to display. Otherwise all frames will be 77 | dumped. 78 | 79 | Syntax: v8 bt [number] 80 | findjsinstances -- List all objects which share the specified map. 81 | Accepts the same options as `v8 inspect` 82 | findjsobjects -- List all object types and instance counts grouped by map and sorted by instance count. 83 | Requires `LLNODE_RANGESFILE` environment variable to be set to a file containing memory ranges for the 84 | core file being debugged. 85 | There are scripts for generating this file on Linux and Mac in the scripts directory of the llnode 86 | repository. 87 | findrefs -- Find all the objects that refer to the specified object. 88 | inspect -- Print detailed description and contents of the JavaScript value. 89 | 90 | Possible flags (all optional): 91 | 92 | * -F, --full-string - print whole string without adding ellipsis 93 | * -m, --print-map - print object's map address 94 | * -s, --print-source - print source code for function objects 95 | * --string-length num - print maximum of `num` characters in string 96 | 97 | Syntax: v8 inspect [flags] expr 98 | nodeinfo -- Print information about Node.js 99 | print -- Print short description of the JavaScript value. 100 | 101 | Syntax: v8 print expr 102 | source -- Source code information 103 | 104 | For more help on any particular subcommand, type 'help '. 105 | ``` 106 | 107 | 108 | ## LICENSE 109 | 110 | This software is licensed under the MIT License. 111 | 112 | Copyright Fedor Indutny, 2016. 113 | 114 | Permission is hereby granted, free of charge, to any person obtaining a 115 | copy of this software and associated documentation files (the 116 | "Software"), to deal in the Software without restriction, including 117 | without limitation the rights to use, copy, modify, merge, publish, 118 | distribute, sublicense, and/or sell copies of the Software, and to permit 119 | persons to whom the Software is furnished to do so, subject to the 120 | following conditions: 121 | 122 | The above copyright notice and this permission notice shall be included 123 | in all copies or substantial portions of the Software. 124 | 125 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 126 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 127 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 128 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 129 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 130 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 131 | USE OR OTHER DEALINGS IN THE SOFTWARE. 132 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ "target_name": "none", "type": "none" }] 3 | } 4 | -------------------------------------------------------------------------------- /common.gypi: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "visibility%": "hidden", # V8"s visibility setting 4 | "target_arch%": "ia32", # set v8"s target architecture 5 | "host_arch%": "ia32", # set v8"s host architecture 6 | "library%": "static_library", # allow override to "shared_library" for DLL/.so builds 7 | "uv_library%": "static_library", # allow override to "shared_library" for DLL/.so builds 8 | "component%": "static_library", # NB. these names match with what V8 expects 9 | "msvs_multi_core_compile": "0", # we do enable multicore compiles, but not using the V8 way 10 | "gcc_version%": "unknown", 11 | "clang%": 1, 12 | "lldb_dir%": "lldb", 13 | "lldb_lib%": "lldb", 14 | "conditions": [ 15 | ["GENERATOR == 'ninja'", { 16 | "OBJ_DIR": "<(PRODUCT_DIR)/obj", 17 | }, { 18 | "OBJ_DIR": "<(PRODUCT_DIR)/obj.target", 19 | }], 20 | ], 21 | }, 22 | 23 | "target_defaults": { 24 | "default_configuration": "Release", 25 | "configurations": { 26 | "Debug": { 27 | "defines": [ "DEBUG", "_DEBUG" ], 28 | "cflags": [ "-g", "-O0", "-fwrapv" ], 29 | "xcode_settings": { 30 | "GCC_OPTIMIZATION_LEVEL": "0" 31 | }, 32 | }, 33 | "Release": { 34 | "defines": [ "NDEBUG" ], 35 | "cflags": [ "-g" ], 36 | } 37 | }, 38 | "cflags": [ 39 | "-std=c++11", "-fPIC", 40 | ], 41 | "xcode_settings": { 42 | "GCC_VERSION": "com.apple.compilers.llvm.clang.1_0", 43 | "GCC_WARN_ABOUT_MISSING_NEWLINE": "YES", # -Wnewline-eof 44 | "PREBINDING": "NO", # No -Wl,-prebind 45 | "OTHER_CFLAGS": [ 46 | "-std=c++11", 47 | "-fstrict-aliasing", 48 | "-g", 49 | ], 50 | "WARNING_CFLAGS": [ 51 | "-Wall", 52 | "-Wendif-labels", 53 | "-W", 54 | "-Wno-unused-parameter", 55 | "-Wundeclared-selector", 56 | ], 57 | }, 58 | "conditions": [ 59 | ["target_arch=='ia32'", { 60 | "xcode_settings": {"ARCHS": ["i386"]}, 61 | }], 62 | ["target_arch=='x64'", { 63 | "xcode_settings": {"ARCHS": ["x86_64"]}, 64 | }], 65 | [ "OS in 'linux freebsd openbsd solaris'", { 66 | "target_conditions": [ 67 | ["_type=='static_library'", { 68 | "standalone_static_library": 1, # disable thin archive which needs binutils >= 2.19 69 | }], 70 | ], 71 | "conditions": [ 72 | [ "target_arch=='ia32'", { 73 | "cflags": [ "-m32" ], 74 | "ldflags": [ "-m32" ], 75 | }], 76 | [ "target_arch=='x64'", { 77 | "cflags": [ "-m64" ], 78 | "ldflags": [ "-m64" ], 79 | }], 80 | ], 81 | }], 82 | ] 83 | }, 84 | } 85 | -------------------------------------------------------------------------------- /gyp_llnode: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import glob 4 | import platform 5 | import os 6 | import subprocess 7 | import sys 8 | 9 | CC = os.environ.get('CC', 'cc') 10 | script_dir = os.path.dirname(__file__) 11 | root = os.path.normpath(script_dir) 12 | output_dir = os.path.join(os.path.abspath(root), 'out') 13 | 14 | sys.path.insert(0, os.path.join(root, 'tools', 'gyp', 'pylib')) 15 | try: 16 | import gyp 17 | except ImportError: 18 | print('You need to install gyp in tools/gyp first, run:') 19 | print(' svn co http://gyp.googlecode.com/svn/trunk tools/gyp'); 20 | print('or') 21 | print(' git clone https://chromium.googlesource.com/external/gyp.git ' + 22 | 'tools/gyp') 23 | sys.exit(42) 24 | 25 | 26 | def host_arch(): 27 | machine = platform.machine() 28 | if machine == 'i386': return 'ia32' 29 | if machine == 'x86_64': return 'x64' 30 | if machine.startswith('arm'): return 'arm' 31 | if machine.startswith('mips'): return 'mips' 32 | return machine # Return as-is and hope for the best. 33 | 34 | 35 | def compiler_version(): 36 | proc = subprocess.Popen(CC.split() + ['--version'], stdout=subprocess.PIPE) 37 | is_clang = 'clang' in proc.communicate()[0].split('\n')[0] 38 | proc = subprocess.Popen(CC.split() + ['-dumpversion'], stdout=subprocess.PIPE) 39 | version = proc.communicate()[0].split('.') 40 | version = map(int, version[:2]) 41 | version = tuple(version) 42 | return (version, is_clang) 43 | 44 | 45 | def run_gyp(args): 46 | rc = gyp.main(args) 47 | if rc != 0: 48 | print 'Error running GYP' 49 | sys.exit(rc) 50 | 51 | 52 | if __name__ == '__main__': 53 | args = sys.argv[1:] 54 | 55 | # GYP bug. 56 | # On msvs it will crash if it gets an absolute path. 57 | # On Mac/make it will crash if it doesn't get an absolute path. 58 | if sys.platform == 'win32': 59 | args.append(os.path.join(root, 'llnode.gyp')) 60 | common_fn = os.path.join(root, 'common.gypi') 61 | options_fn = os.path.join(root, 'options.gypi') 62 | # we force vs 2010 over 2008 which would otherwise be the default for gyp 63 | if not os.environ.get('GYP_MSVS_VERSION'): 64 | os.environ['GYP_MSVS_VERSION'] = '2010' 65 | else: 66 | args.append(os.path.join(os.path.abspath(root), 'llnode.gyp')) 67 | common_fn = os.path.join(os.path.abspath(root), 'common.gypi') 68 | options_fn = os.path.join(os.path.abspath(root), 'options.gypi') 69 | 70 | if os.path.exists(common_fn): 71 | args.extend(['-I', common_fn]) 72 | 73 | if os.path.exists(options_fn): 74 | args.extend(['-I', options_fn]) 75 | 76 | args.append('--depth=' + root) 77 | 78 | # There's a bug with windows which doesn't allow this feature. 79 | if sys.platform != 'win32': 80 | if '-f' not in args: 81 | args.extend('-f make'.split()) 82 | if 'ninja' not in args: 83 | args.extend(['-Goutput_dir=' + output_dir]) 84 | args.extend(['--generator-output', output_dir]) 85 | (major, minor), is_clang = compiler_version() 86 | args.append('-Dgcc_version=%d' % (10 * major + minor)) 87 | args.append('-Dclang=%d' % int(is_clang)) 88 | 89 | if not any(a.startswith('-Dhost_arch=') for a in args): 90 | args.append('-Dhost_arch=%s' % host_arch()) 91 | 92 | if not any(a.startswith('-Dtarget_arch=') for a in args): 93 | args.append('-Dtarget_arch=%s' % host_arch()) 94 | 95 | gyp_args = list(args) 96 | print gyp_args 97 | run_gyp(gyp_args) 98 | -------------------------------------------------------------------------------- /llnode.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | # gyp does not appear to let you test for undefined variables, so define 4 | # lldb_build_dir as empty so we can test it later. 5 | "lldb_build_dir%": "" 6 | }, 7 | 8 | "targets": [{ 9 | "target_name": "llnode", 10 | "type": "shared_library", 11 | "product_prefix": "", 12 | 13 | "include_dirs": [ 14 | ".", 15 | "<(lldb_dir)/include", 16 | ], 17 | 18 | "sources": [ 19 | "src/llnode.cc", 20 | "src/llv8.cc", 21 | "src/llv8-constants.cc", 22 | "src/llscan.cc", 23 | ], 24 | 25 | "conditions": [ 26 | [ "OS == 'mac'", { 27 | "conditions": [ 28 | [ "lldb_build_dir == ''", { 29 | "variables": { 30 | "mac_shared_frameworks": "/Applications/Xcode.app/Contents/SharedFrameworks", 31 | }, 32 | "xcode_settings": { 33 | "OTHER_LDFLAGS": [ 34 | "-F<(mac_shared_frameworks)", 35 | "-Wl,-rpath,<(mac_shared_frameworks)", 36 | "-framework LLDB", 37 | ], 38 | }, 39 | }, 40 | # lldb_builddir != "" 41 | { 42 | "xcode_settings": { 43 | "OTHER_LDFLAGS": [ 44 | "-Wl,-rpath,<(lldb_build_dir)/lib", 45 | "-L<(lldb_build_dir)/lib", 46 | "-l<(lldb_lib)", 47 | ], 48 | }, 49 | }], 50 | ], 51 | }], 52 | ] 53 | }], 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "llnode", 3 | "version": "1.3.0", 4 | "description": "llnode test suite", 5 | "main": "no-entry-sorry.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "tape test/*-test.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+ssh://git@github.com/indutny/llnode.git" 15 | }, 16 | "keywords": [ 17 | "llnode", 18 | "post", 19 | "mortem" 20 | ], 21 | "author": "Fedor Indutny ", 22 | "license": "MIT", 23 | "gypfile": true, 24 | "bugs": { 25 | "url": "https://github.com/indutny/llnode/issues" 26 | }, 27 | "homepage": "https://github.com/indutny/llnode#readme", 28 | "devDependencies": { 29 | "tape": "^4.4.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /scripts/otool2segments.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Created on 7 Apr 2016 4 | 5 | @author: hhellyer 6 | ''' 7 | import sys,os,subprocess 8 | 9 | OTOOL_COMMAND = "otool -l {0}" 10 | 11 | # Grab the details of the memory ranges in the process from otool. 12 | def main(): 13 | if( len(sys.argv) < 2 ): 14 | print("Usage " + sys.argv[0] + " ") 15 | sys.exit(1) 16 | core_file = sys.argv[1] 17 | 18 | otool_proc = subprocess.Popen( 19 | OTOOL_COMMAND.format(core_file), shell=True, bufsize=1, 20 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) 21 | (otool_stdin, otool_stdout) = (otool_proc.stdin, otool_proc.stdout) 22 | 23 | reading_segment = False 24 | vaddress = "" 25 | 26 | for line in otool_stdout: 27 | line = line.strip() 28 | if not line.startswith("vmaddr") and not reading_segment: 29 | continue 30 | elif line.startswith("vmaddr"): 31 | # Might need to filer out segments that have a file offset of 0 32 | # (ie segments that aren't in the core!) 33 | reading_segment = True 34 | (name, vaddress) = line.split() 35 | elif line.startswith("vmsize") and reading_segment: 36 | reading_segment = False 37 | memsize = line.split()[1] 38 | # Simple format "address size", both in hex 39 | print("{0} {1}".format(vaddress, memsize)) 40 | vaddress = "" 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /scripts/readelf2segments.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Created on 7 Apr 2016 4 | 5 | @author: hhellyer 6 | ''' 7 | import sys,os,subprocess 8 | 9 | READELF_COMMAND = "readelf --segments {0}" 10 | 11 | # Grab the details of the memory ranges in the process from readelf. 12 | def main(): 13 | if( len(sys.argv) < 2 ): 14 | print("Usage " + sys.argv[0] + " ") 15 | sys.exit(1) 16 | core_file = sys.argv[1] 17 | 18 | readelf_proc = subprocess.Popen( 19 | READELF_COMMAND.format(core_file), shell=True, bufsize=1, 20 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) 21 | (readelf_stdin, readelf_stdout) = (readelf_proc.stdin, readelf_proc.stdout) 22 | 23 | reading_segment = False 24 | vaddress = "" 25 | 26 | for line in readelf_stdout: 27 | line = line.strip() 28 | if not line.startswith("LOAD") and not reading_segment: 29 | continue 30 | elif line.startswith("LOAD"): 31 | # Might need to filer out segments that have a file offset of 0 32 | # (ie segments that aren't in the core!) 33 | reading_segment = True 34 | (type, offset, vaddress, paddr) = line.split() 35 | elif reading_segment: 36 | reading_segment = False 37 | memsize = line.split()[1] 38 | # Simple format "address size", both in hex 39 | print("{0} {1}".format(vaddress, memsize)) 40 | vaddress = "" 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /src/llnode.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "src/llnode.h" 9 | #include "src/llscan.h" 10 | #include "src/llv8.h" 11 | 12 | namespace llnode { 13 | 14 | using namespace lldb; 15 | 16 | v8::LLV8 llv8; 17 | 18 | char** CommandBase::ParseInspectOptions(char** cmd, 19 | v8::Value::InspectOptions* options) { 20 | static struct option opts[] = { 21 | {"full-string", no_argument, nullptr, 'F'}, 22 | {"string-length", required_argument, nullptr, 0x1001}, 23 | {"print-map", no_argument, nullptr, 'm'}, 24 | {"print-source", no_argument, nullptr, 's'}, 25 | {nullptr, 0, nullptr, 0}}; 26 | 27 | int argc = 1; 28 | for (char** p = cmd; p != nullptr && *p != nullptr; p++) argc++; 29 | 30 | char* args[argc]; 31 | 32 | // Make this look like a command line, we need a valid element at index 0 33 | // for getopt_long to use in its error messages. 34 | char name[] = "llnode"; 35 | args[0] = name; 36 | for (int i = 0; i < argc - 1; i++) args[i + 1] = cmd[i]; 37 | 38 | // Reset getopts. 39 | optind = 1; 40 | opterr = 1; 41 | do { 42 | int arg = getopt_long(argc, args, "Fms", opts, nullptr); 43 | if (arg == -1) break; 44 | 45 | switch (arg) { 46 | case 'F': 47 | options->string_length = 0; 48 | break; 49 | case 'm': 50 | options->print_map = true; 51 | break; 52 | case 0x1001: 53 | options->string_length = strtol(optarg, nullptr, 10); 54 | break; 55 | case 's': 56 | options->print_source = true; 57 | break; 58 | default: 59 | continue; 60 | } 61 | } while (true); 62 | 63 | // Use the original cmd array for our return value. 64 | return &cmd[optind - 1]; 65 | } 66 | 67 | 68 | bool BacktraceCmd::DoExecute(SBDebugger d, char** cmd, 69 | SBCommandReturnObject& result) { 70 | SBTarget target = d.GetSelectedTarget(); 71 | SBThread thread = target.GetProcess().GetSelectedThread(); 72 | if (!thread.IsValid()) { 73 | result.SetError("No valid process, please start something\n"); 74 | return false; 75 | } 76 | 77 | errno = 0; 78 | int number = 79 | (cmd != nullptr && *cmd != nullptr) ? strtol(*cmd, nullptr, 10) : -1; 80 | if ((number == 0 && errno == EINVAL) || (number < 0 && number != -1)) { 81 | result.SetError("Invalid number of frames"); 82 | return false; 83 | } 84 | 85 | // Load V8 constants from postmortem data 86 | llv8.Load(target); 87 | 88 | { 89 | SBStream desc; 90 | if (!thread.GetDescription(desc)) return false; 91 | result.Printf(" * %s", desc.GetData()); 92 | } 93 | 94 | SBFrame selected_frame = thread.GetSelectedFrame(); 95 | 96 | uint32_t num_frames = thread.GetNumFrames(); 97 | if (number != -1) num_frames = number; 98 | for (uint32_t i = 0; i < num_frames; i++) { 99 | SBFrame frame = thread.GetFrameAtIndex(i); 100 | SBSymbol symbol = frame.GetSymbol(); 101 | 102 | // C++ symbol 103 | if (symbol.IsValid()) { 104 | SBStream desc; 105 | if (!frame.GetDescription(desc)) continue; 106 | result.Printf(frame == selected_frame ? " * %s" : " %s", 107 | desc.GetData()); 108 | continue; 109 | } 110 | 111 | // V8 frame 112 | v8::Error err; 113 | v8::JSFrame v8_frame(&llv8, static_cast(frame.GetFP())); 114 | std::string res = v8_frame.Inspect(true, err); 115 | 116 | // Skip invalid frames 117 | if (err.Fail()) continue; 118 | 119 | // V8 symbol 120 | result.Printf(frame == selected_frame ? " * frame #%u: 0x%016llx %s\n" 121 | : " frame #%u: 0x%016llx %s\n", 122 | i, static_cast(frame.GetPC()), 123 | res.c_str()); 124 | } 125 | 126 | result.SetStatus(eReturnStatusSuccessFinishResult); 127 | return true; 128 | } 129 | 130 | 131 | bool PrintCmd::DoExecute(SBDebugger d, char** cmd, 132 | SBCommandReturnObject& result) { 133 | if (*cmd == NULL) { 134 | if (detailed_) { 135 | result.SetError("USAGE: v8 inspect [flags] expr\n"); 136 | } else { 137 | result.SetError("USAGE: v8 print expr\n"); 138 | } 139 | return false; 140 | } 141 | 142 | SBTarget target = d.GetSelectedTarget(); 143 | if (!target.IsValid()) { 144 | result.SetError("No valid process, please start something\n"); 145 | return false; 146 | } 147 | 148 | v8::Value::InspectOptions inspect_options; 149 | 150 | inspect_options.detailed = detailed_; 151 | 152 | char** start = ParseInspectOptions(cmd, &inspect_options); 153 | 154 | std::string full_cmd; 155 | for (; start != nullptr && *start != nullptr; start++) full_cmd += *start; 156 | 157 | SBExpressionOptions options; 158 | SBValue value = target.EvaluateExpression(full_cmd.c_str(), options); 159 | if (value.GetError().Fail()) { 160 | SBStream desc; 161 | if (value.GetError().GetDescription(desc)) { 162 | result.SetError(desc.GetData()); 163 | } 164 | result.SetStatus(eReturnStatusFailed); 165 | return false; 166 | } 167 | 168 | // Load V8 constants from postmortem data 169 | llv8.Load(target); 170 | 171 | v8::Value v8_value(&llv8, value.GetValueAsSigned()); 172 | v8::Error err; 173 | std::string res = v8_value.Inspect(&inspect_options, err); 174 | if (err.Fail()) { 175 | result.SetError("Failed to evaluate expression"); 176 | return false; 177 | } 178 | 179 | result.Printf("%s\n", res.c_str()); 180 | result.SetStatus(eReturnStatusSuccessFinishResult); 181 | return true; 182 | } 183 | 184 | 185 | bool ListCmd::DoExecute(SBDebugger d, char** cmd, 186 | SBCommandReturnObject& result) { 187 | static SBFrame last_frame; 188 | static uint64_t last_line = 0; 189 | SBTarget target = d.GetSelectedTarget(); 190 | SBThread thread = target.GetProcess().GetSelectedThread(); 191 | if (!thread.IsValid()) { 192 | result.SetError("No valid process, please start something\n"); 193 | return false; 194 | } 195 | 196 | std::string full_cmd; 197 | bool grab_line = false; 198 | bool line_switch = false; 199 | int line_from_switch = 0; 200 | for (char** start = cmd; *start != nullptr; start++) { 201 | if (grab_line) { 202 | grab_line = false; 203 | line_switch = true; 204 | errno = 0; 205 | line_from_switch = strtol(*start, nullptr, 10); 206 | if (errno) { 207 | result.SetError("Invalid line number"); 208 | return false; 209 | } 210 | line_from_switch--; 211 | } 212 | if (strcmp(*start, "-l") == 0) { 213 | grab_line = true; 214 | } 215 | full_cmd += *start; 216 | } 217 | if (grab_line || (line_switch && line_from_switch < 0)) { 218 | result.SetError("Expected line number after -l"); 219 | return false; 220 | } 221 | 222 | // Load V8 constants from postmortem data 223 | llv8.Load(target); 224 | SBFrame frame = thread.GetSelectedFrame(); 225 | SBSymbol symbol = frame.GetSymbol(); 226 | 227 | bool reset_line = false; 228 | if (line_switch) { 229 | reset_line = true; 230 | last_line = line_from_switch; 231 | } else if (frame != last_frame) { 232 | last_line = 0; 233 | reset_line = true; 234 | } 235 | last_frame = frame; 236 | // C++ symbol 237 | if (symbol.IsValid()) { 238 | SBCommandInterpreter interpreter = d.GetCommandInterpreter(); 239 | std::string cmd = "source list "; 240 | cmd += full_cmd; 241 | interpreter.HandleCommand(cmd.c_str(), result, false); 242 | return true; 243 | } 244 | 245 | // V8 frame 246 | v8::Error err; 247 | v8::JSFrame v8_frame(&llv8, static_cast(frame.GetFP())); 248 | 249 | const static uint32_t kDisplayLines = 4; 250 | std::string* lines = new std::string[kDisplayLines]; 251 | uint32_t lines_found = 0; 252 | 253 | uint32_t line_cursor = v8_frame.GetSourceForDisplay( 254 | reset_line, last_line, kDisplayLines, lines, lines_found, err); 255 | if (err.Fail()) { 256 | result.SetError(err.GetMessage()); 257 | return false; 258 | } 259 | last_line = line_cursor; 260 | 261 | for (uint32_t i = 0; i < lines_found; i++) { 262 | result.Printf(" %d %s\n", line_cursor - lines_found + i + 1, 263 | lines[i].c_str()); 264 | } 265 | result.SetStatus(eReturnStatusSuccessFinishResult); 266 | return true; 267 | } 268 | 269 | } // namespace llnode 270 | 271 | namespace lldb { 272 | 273 | bool PluginInitialize(SBDebugger d) { 274 | SBCommandInterpreter interpreter = d.GetCommandInterpreter(); 275 | 276 | SBCommand v8 = interpreter.AddMultiwordCommand("v8", "Node.js helpers"); 277 | 278 | v8.AddCommand( 279 | "bt", new llnode::BacktraceCmd(), 280 | "Show a backtrace with node.js JavaScript functions and their args. " 281 | "An optional argument is accepted; if that argument is a number, it " 282 | "specifies the number of frames to display. Otherwise all frames will " 283 | "be dumped.\n\n" 284 | "Syntax: v8 bt [number]\n"); 285 | interpreter.AddCommand("jsstack", new llnode::BacktraceCmd(), 286 | "Alias for `v8 bt`"); 287 | 288 | v8.AddCommand("print", new llnode::PrintCmd(false), 289 | "Print short description of the JavaScript value.\n\n" 290 | "Syntax: v8 print expr\n"); 291 | 292 | v8.AddCommand( 293 | "inspect", new llnode::PrintCmd(true), 294 | "Print detailed description and contents of the JavaScript value.\n\n" 295 | "Possible flags (all optional):\n\n" 296 | " * -F, --full-string - print whole string without adding ellipsis\n" 297 | " * -m, --print-map - print object's map address\n" 298 | " * -s, --print-source - print source code for function objects\n" 299 | " * --string-length num - print maximum of `num` characters in string\n" 300 | "\n" 301 | "Syntax: v8 inspect [flags] expr\n"); 302 | interpreter.AddCommand("jsprint", new llnode::PrintCmd(true), 303 | "Alias for `v8 inspect`"); 304 | 305 | SBCommand source = 306 | v8.AddMultiwordCommand("source", "Source code information"); 307 | source.AddCommand("list", new llnode::ListCmd(), 308 | "Print source lines around a selected JavaScript frame.\n\n" 309 | "Syntax: v8 source list\n"); 310 | interpreter.AddCommand("jssource", new llnode::ListCmd(), 311 | "Alias for `v8 source list`"); 312 | 313 | v8.AddCommand("findjsobjects", new llnode::FindObjectsCmd(), 314 | "List all object types and instance counts grouped by map and " 315 | "sorted by instance count.\n" 316 | "Requires `LLNODE_RANGESFILE` environment variable to be set " 317 | "to a file containing memory ranges for the core file being " 318 | "debugged.\n" 319 | "There are scripts for generating this file on Linux and Mac " 320 | "in the scripts directory of the llnode repository."); 321 | 322 | interpreter.AddCommand("findjsobjects", new llnode::FindObjectsCmd(), 323 | "Alias for `v8 findjsobjects`"); 324 | 325 | v8.AddCommand("findjsinstances", new llnode::FindInstancesCmd(), 326 | "List all objects which share the specified map.\n" 327 | "Accepts the same options as `v8 inspect`"); 328 | 329 | interpreter.AddCommand("findjsinstances", new llnode::FindInstancesCmd(), 330 | "List all objects which share the specified map.\n"); 331 | 332 | v8.AddCommand("nodeinfo", new llnode::NodeInfoCmd(), 333 | "Print information about Node.js\n"); 334 | 335 | v8.AddCommand( 336 | "findrefs", new llnode::FindReferencesCmd(), 337 | "Finds all the object properties which meet the search criteria.\n" 338 | "The default is to list all the object properties that reference the " 339 | "specified value.\n" 340 | "Flags:\n\n" 341 | " * -v, --value expr - all properties that refer to the specified " 342 | "JavaScript object (default)\n" 343 | " * -n, --name name - all properties with the specified name\n" 344 | " * -s, --string string - all properties that refer to the specified " 345 | "JavaScript string value\n" 346 | "\n"); 347 | 348 | return true; 349 | } 350 | 351 | } // namespace lldb 352 | -------------------------------------------------------------------------------- /src/llnode.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LLNODE_H_ 2 | #define SRC_LLNODE_H_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "src/llv8.h" 9 | 10 | namespace llnode { 11 | 12 | class CommandBase : public lldb::SBCommandPluginInterface { 13 | public: 14 | char** ParseInspectOptions(char** cmd, v8::Value::InspectOptions* options); 15 | }; 16 | 17 | class BacktraceCmd : public CommandBase { 18 | public: 19 | ~BacktraceCmd() override {} 20 | 21 | bool DoExecute(lldb::SBDebugger d, char** cmd, 22 | lldb::SBCommandReturnObject& result) override; 23 | }; 24 | 25 | class PrintCmd : public CommandBase { 26 | public: 27 | PrintCmd(bool detailed) : detailed_(detailed) {} 28 | 29 | ~PrintCmd() override {} 30 | 31 | bool DoExecute(lldb::SBDebugger d, char** cmd, 32 | lldb::SBCommandReturnObject& result) override; 33 | 34 | private: 35 | bool detailed_; 36 | }; 37 | 38 | class ListCmd : public CommandBase { 39 | public: 40 | ~ListCmd() override {} 41 | 42 | bool DoExecute(lldb::SBDebugger d, char** cmd, 43 | lldb::SBCommandReturnObject& result) override; 44 | }; 45 | 46 | } // namespace llnode 47 | 48 | #endif // SRC_LLNODE_H_ 49 | -------------------------------------------------------------------------------- /src/llscan.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "src/llnode.h" 14 | #include "src/llscan.h" 15 | #include "src/llv8-inl.h" 16 | #include "src/llv8.h" 17 | 18 | namespace llnode { 19 | 20 | // Defined in llnode.cc 21 | extern v8::LLV8 llv8; 22 | 23 | LLScan llscan; 24 | 25 | using namespace lldb; 26 | 27 | 28 | bool FindObjectsCmd::DoExecute(SBDebugger d, char** cmd, 29 | SBCommandReturnObject& result) { 30 | SBTarget target = d.GetSelectedTarget(); 31 | if (!target.IsValid()) { 32 | result.SetError("No valid process, please start something\n"); 33 | return false; 34 | } 35 | 36 | /* Ensure we have a map of objects. */ 37 | if (!llscan.ScanHeapForObjects(target, result)) { 38 | result.SetStatus(eReturnStatusFailed); 39 | return false; 40 | } 41 | 42 | /* Create a vector to hold the entries sorted by instance count 43 | * TODO(hhellyer) - Make sort type an option (by count, size or name) 44 | */ 45 | std::vector sorted_by_count; 46 | TypeRecordMap::iterator end = llscan.GetMapsToInstances().end(); 47 | for (TypeRecordMap::iterator it = llscan.GetMapsToInstances().begin(); 48 | it != end; ++it) { 49 | sorted_by_count.push_back(it->second); 50 | } 51 | 52 | std::sort(sorted_by_count.begin(), sorted_by_count.end(), 53 | TypeRecord::CompareInstanceCounts); 54 | 55 | uint64_t total_objects = 0; 56 | 57 | result.Printf(" Instances Total Size Name\n"); 58 | result.Printf(" ---------- ---------- ----\n"); 59 | 60 | for (std::vector::iterator it = sorted_by_count.begin(); 61 | it != sorted_by_count.end(); ++it) { 62 | TypeRecord* t = *it; 63 | result.Printf(" %10" PRId64 " %10" PRId64 " %s\n", t->GetInstanceCount(), 64 | t->GetTotalInstanceSize(), t->GetTypeName().c_str()); 65 | total_objects += t->GetInstanceCount(); 66 | } 67 | 68 | result.SetStatus(eReturnStatusSuccessFinishResult); 69 | return true; 70 | } 71 | 72 | 73 | bool FindInstancesCmd::DoExecute(SBDebugger d, char** cmd, 74 | SBCommandReturnObject& result) { 75 | if (*cmd == NULL) { 76 | result.SetError("USAGE: v8 findjsinstances [-Fm] instance_name\n"); 77 | return false; 78 | } 79 | 80 | SBTarget target = d.GetSelectedTarget(); 81 | if (!target.IsValid()) { 82 | result.SetError("No valid process, please start something\n"); 83 | return false; 84 | } 85 | 86 | /* Ensure we have a map of objects. */ 87 | if (!llscan.ScanHeapForObjects(target, result)) { 88 | result.SetStatus(eReturnStatusFailed); 89 | return false; 90 | } 91 | 92 | v8::Value::InspectOptions inspect_options; 93 | 94 | inspect_options.detailed = detailed_; 95 | 96 | char** start = ParseInspectOptions(cmd, &inspect_options); 97 | 98 | std::string full_cmd; 99 | for (; start != nullptr && *start != nullptr; start++) full_cmd += *start; 100 | 101 | std::string type_name = full_cmd; 102 | 103 | // Load V8 constants from postmortem data 104 | llv8.Load(target); 105 | 106 | TypeRecordMap::iterator instance_it = 107 | llscan.GetMapsToInstances().find(type_name); 108 | if (instance_it != llscan.GetMapsToInstances().end()) { 109 | TypeRecord* t = instance_it->second; 110 | for (std::set::iterator it = t->GetInstances().begin(); 111 | it != t->GetInstances().end(); ++it) { 112 | v8::Error err; 113 | v8::Value v8_value(&llv8, *it); 114 | std::string res = v8_value.Inspect(&inspect_options, err); 115 | result.Printf("%s\n", res.c_str()); 116 | } 117 | 118 | } else { 119 | result.Printf("No objects found with type name %s\n", type_name.c_str()); 120 | result.SetStatus(eReturnStatusFailed); 121 | return false; 122 | } 123 | 124 | result.SetStatus(eReturnStatusSuccessFinishResult); 125 | return true; 126 | } 127 | 128 | 129 | bool NodeInfoCmd::DoExecute(SBDebugger d, char** cmd, 130 | SBCommandReturnObject& result) { 131 | SBTarget target = d.GetSelectedTarget(); 132 | if (!target.IsValid()) { 133 | result.SetError("No valid process, please start something\n"); 134 | return false; 135 | } 136 | 137 | /* Ensure we have a map of objects. */ 138 | if (!llscan.ScanHeapForObjects(target, result)) { 139 | return false; 140 | } 141 | 142 | std::string process_type_name("process"); 143 | 144 | TypeRecordMap::iterator instance_it = 145 | llscan.GetMapsToInstances().find(process_type_name); 146 | 147 | if (instance_it != llscan.GetMapsToInstances().end()) { 148 | TypeRecord* t = instance_it->second; 149 | for (std::set::iterator it = t->GetInstances().begin(); 150 | it != t->GetInstances().end(); ++it) { 151 | v8::Error err; 152 | 153 | // The properties object should be a JSObject 154 | v8::JSObject process_obj(&llv8, *it); 155 | 156 | 157 | v8::Value pid_val = process_obj.GetProperty("pid", err); 158 | 159 | if (pid_val.v8() != nullptr) { 160 | v8::Smi pid_smi(pid_val); 161 | result.Printf("Information for process id %" PRId64 162 | " (process=0x%" PRIx64 ")\n", 163 | pid_smi.GetValue(), process_obj.raw()); 164 | } else { 165 | // This isn't the process object we are looking for. 166 | continue; 167 | } 168 | 169 | v8::Value platform_val = process_obj.GetProperty("platform", err); 170 | 171 | if (platform_val.v8() != nullptr) { 172 | v8::String platform_str(platform_val); 173 | result.Printf("Platform = %s, ", platform_str.ToString(err).c_str()); 174 | } 175 | 176 | v8::Value arch_val = process_obj.GetProperty("arch", err); 177 | 178 | if (arch_val.v8() != nullptr) { 179 | v8::String arch_str(arch_val); 180 | result.Printf("Architecture = %s, ", arch_str.ToString(err).c_str()); 181 | } 182 | 183 | v8::Value ver_val = process_obj.GetProperty("version", err); 184 | 185 | if (ver_val.v8() != nullptr) { 186 | v8::String ver_str(ver_val); 187 | result.Printf("Node Version = %s\n", ver_str.ToString(err).c_str()); 188 | } 189 | 190 | // Note the extra s on versions! 191 | v8::Value versions_val = process_obj.GetProperty("versions", err); 192 | if (versions_val.v8() != nullptr) { 193 | v8::JSObject versions_obj(versions_val); 194 | 195 | std::vector version_keys; 196 | 197 | // Get the list of keys on an object as strings. 198 | versions_obj.Keys(version_keys, err); 199 | 200 | std::sort(version_keys.begin(), version_keys.end()); 201 | 202 | result.Printf("Component versions (process.versions=0x%" PRIx64 "):\n", 203 | versions_val.raw()); 204 | 205 | for (std::vector::iterator key = version_keys.begin(); 206 | key != version_keys.end(); ++key) { 207 | v8::Value ver_val = versions_obj.GetProperty(*key, err); 208 | if (ver_val.v8() != nullptr) { 209 | v8::String ver_str(ver_val); 210 | result.Printf(" %s = %s\n", key->c_str(), 211 | ver_str.ToString(err).c_str()); 212 | } 213 | } 214 | } 215 | 216 | v8::Value release_val = process_obj.GetProperty("release", err); 217 | if (release_val.v8() != nullptr) { 218 | v8::JSObject release_obj(release_val); 219 | 220 | std::vector release_keys; 221 | 222 | // Get the list of keys on an object as strings. 223 | release_obj.Keys(release_keys, err); 224 | 225 | result.Printf("Release Info (process.release=0x%" PRIx64 "):\n", 226 | release_val.raw()); 227 | 228 | for (std::vector::iterator key = release_keys.begin(); 229 | key != release_keys.end(); ++key) { 230 | v8::Value ver_val = release_obj.GetProperty(*key, err); 231 | if (ver_val.v8() != nullptr) { 232 | v8::String ver_str(ver_val); 233 | result.Printf(" %s = %s\n", key->c_str(), 234 | ver_str.ToString(err).c_str()); 235 | } 236 | } 237 | } 238 | 239 | v8::Value execPath_val = process_obj.GetProperty("execPath", err); 240 | 241 | if (execPath_val.v8() != nullptr) { 242 | v8::String execPath_str(execPath_val); 243 | result.Printf("Executable Path = %s\n", 244 | execPath_str.ToString(err).c_str()); 245 | } 246 | 247 | v8::Value argv_val = process_obj.GetProperty("argv", err); 248 | 249 | if (argv_val.v8() != nullptr) { 250 | v8::JSArray argv_arr(argv_val); 251 | result.Printf("Command line arguments (process.argv=0x%" PRIx64 "):\n", 252 | argv_val.raw()); 253 | // argv is an array, which we can treat as a subtype of object. 254 | int64_t length = argv_arr.GetArrayLength(err); 255 | for (int64_t i = 0; i < length; ++i) { 256 | v8::Value element_val = argv_arr.GetArrayElement(i, err); 257 | if (element_val.v8() != nullptr) { 258 | v8::String element_str(element_val); 259 | result.Printf(" [%" PRId64 "] = '%s'\n", i, 260 | element_str.ToString(err).c_str()); 261 | } 262 | } 263 | } 264 | 265 | /* The docs for process.execArgv say "These options are useful in order 266 | * to spawn child processes with the same execution environment 267 | * as the parent." so being able to check these have been passed in 268 | * seems like a good idea. 269 | */ 270 | v8::Value execArgv_val = process_obj.GetProperty("execArgv", err); 271 | 272 | if (argv_val.v8() != nullptr) { 273 | // Should possibly just treat this as an object in case anyone has 274 | // attached a property. 275 | v8::JSArray execArgv_arr(execArgv_val); 276 | result.Printf( 277 | "Node.js Comamnd line arguments (process.execArgv=0x%" PRIx64 278 | "):\n", 279 | execArgv_val.raw()); 280 | // execArgv is an array, which we can treat as a subtype of object. 281 | int64_t length = execArgv_arr.GetArrayLength(err); 282 | for (int64_t i = 0; i < length; ++i) { 283 | v8::Value element_val = execArgv_arr.GetArrayElement(i, err); 284 | if (element_val.v8() != nullptr) { 285 | v8::String element_str(element_val); 286 | result.Printf(" [%" PRId64 "] = '%s'\n", i, 287 | element_str.ToString(err).c_str()); 288 | } 289 | } 290 | } 291 | } 292 | 293 | } else { 294 | result.Printf("No process objects found.\n"); 295 | } 296 | 297 | return true; 298 | } 299 | 300 | 301 | bool FindReferencesCmd::DoExecute(SBDebugger d, char** cmd, 302 | SBCommandReturnObject& result) { 303 | if (*cmd == NULL) { 304 | result.SetError("USAGE: v8 findrefs expr\n"); 305 | return false; 306 | } 307 | 308 | SBTarget target = d.GetSelectedTarget(); 309 | if (!target.IsValid()) { 310 | result.SetError("No valid process, please start something\n"); 311 | return false; 312 | } 313 | 314 | // Default scan type. 315 | ScanType type = ScanType::kFieldValue; 316 | 317 | char** start = ParseScanOptions(cmd, &type); 318 | 319 | if (*start == nullptr) { 320 | result.SetError("Missing search parameter"); 321 | result.SetStatus(eReturnStatusFailed); 322 | return false; 323 | } 324 | 325 | // Load V8 constants from postmortem data 326 | llv8.Load(target); 327 | 328 | ObjectScanner* scanner; 329 | 330 | switch (type) { 331 | case ScanType::kFieldValue: { 332 | std::string full_cmd; 333 | for (; start != nullptr && *start != nullptr; start++) full_cmd += *start; 334 | 335 | SBExpressionOptions options; 336 | SBValue value = target.EvaluateExpression(full_cmd.c_str(), options); 337 | if (value.GetError().Fail()) { 338 | SBStream desc; 339 | if (value.GetError().GetDescription(desc)) { 340 | result.SetError(desc.GetData()); 341 | } 342 | result.SetStatus(eReturnStatusFailed); 343 | return false; 344 | } 345 | // Check the address we've been given at least looks like a valid object. 346 | v8::Value search_value(&llv8, value.GetValueAsSigned()); 347 | v8::Smi smi(search_value); 348 | if (smi.Check()) { 349 | result.SetError("Search value is an SMI."); 350 | result.SetStatus(eReturnStatusFailed); 351 | return false; 352 | } 353 | scanner = new ReferenceScanner(search_value); 354 | break; 355 | } 356 | case ScanType::kPropertyName: { 357 | // Check for extra parameters or parameters that needed quoting. 358 | if (start[1] != nullptr) { 359 | result.SetError("Extra search parameter or unquoted string specified."); 360 | result.SetStatus(eReturnStatusFailed); 361 | return false; 362 | } 363 | std::string property_name = start[0]; 364 | scanner = new PropertyScanner(property_name); 365 | break; 366 | } 367 | case ScanType::kStringValue: { 368 | // Check for extra parameters or parameters that needed quoting. 369 | if (start[1] != nullptr) { 370 | result.SetError("Extra search parameter or unquoted string specified."); 371 | result.SetStatus(eReturnStatusFailed); 372 | return false; 373 | } 374 | std::string string_value = start[0]; 375 | scanner = new StringScanner(string_value); 376 | break; 377 | } 378 | /* We can add options to the command and further sub-classes of 379 | * object scanner to do other searches, e.g.: 380 | * - Objects that refer to a particular string literal. 381 | * (lldb) findreferences -s "Hello World!" 382 | */ 383 | case ScanType::kBadOption: { 384 | result.SetError("Invalid search type"); 385 | result.SetStatus(eReturnStatusFailed); 386 | return false; 387 | } 388 | } 389 | 390 | /* Ensure we have a map of objects. 391 | * (Do this after we've checked the options to avoid 392 | * a long pause before reporting an error.) 393 | */ 394 | if (!llscan.ScanHeapForObjects(target, result)) { 395 | delete scanner; 396 | result.SetStatus(eReturnStatusFailed); 397 | return false; 398 | } 399 | 400 | // Walk all the object instances and handle them according to their type. 401 | TypeRecordMap mapstoinstances = llscan.GetMapsToInstances(); 402 | for (auto const entry : mapstoinstances) { 403 | TypeRecord* typerecord = entry.second; 404 | for (uint64_t addr : typerecord->GetInstances()) { 405 | v8::Error err; 406 | v8::Value obj_value(&llv8, addr); 407 | v8::HeapObject heap_object(obj_value); 408 | int64_t type = heap_object.GetType(err); 409 | v8::LLV8* v8 = heap_object.v8(); 410 | 411 | // We only need to handle the types that are in 412 | // FindJSObjectsVisitor::IsAHistogramType 413 | // as those are the only objects that end up in GetMapsToInstances 414 | if (v8::JSObject::IsObjectType(v8, type)) { 415 | // Objects can have elements and arrays can have named properties. 416 | // Basically we need to access objects and arrays as both objects and 417 | // arrays. 418 | v8::JSObject js_obj(heap_object); 419 | scanner->PrintRefs(result, js_obj, err); 420 | 421 | } else if (type < v8->types()->kFirstNonstringType) { 422 | v8::String str(heap_object); 423 | scanner->PrintRefs(result, str, err); 424 | 425 | } else if (type == v8->types()->kJSTypedArrayType) { 426 | // These should only point to off heap memory, 427 | // this case should be a no-op. 428 | } else { 429 | // result.Printf("Unhandled type: %" PRId64 " for addr %" PRIx64 430 | // "\n", type, addr); 431 | } 432 | } 433 | } 434 | 435 | delete scanner; 436 | 437 | result.SetStatus(eReturnStatusSuccessFinishResult); 438 | return true; 439 | } 440 | 441 | 442 | char** FindReferencesCmd::ParseScanOptions(char** cmd, ScanType* type) { 443 | static struct option opts[] = {{"value", no_argument, nullptr, 'v'}, 444 | {"name", no_argument, nullptr, 'n'}, 445 | {"string", no_argument, nullptr, 's'}, 446 | {nullptr, 0, nullptr, 0}}; 447 | 448 | int argc = 1; 449 | for (char** p = cmd; p != nullptr && *p != nullptr; p++) argc++; 450 | 451 | char* args[argc]; 452 | 453 | // Make this look like a command line, we need a valid element at index 0 454 | // for getopt_long to use in its error messages. 455 | char name[] = "llscan"; 456 | args[0] = name; 457 | for (int i = 0; i < argc - 1; i++) args[i + 1] = cmd[i]; 458 | 459 | bool found_scan_type = false; 460 | 461 | // Reset getopts. 462 | optind = 1; 463 | opterr = 1; 464 | do { 465 | int arg = getopt_long(argc, args, "vns", opts, nullptr); 466 | if (arg == -1) break; 467 | 468 | if (found_scan_type) { 469 | *type = ScanType::kBadOption; 470 | break; 471 | } 472 | 473 | switch (arg) { 474 | case 'v': 475 | *type = ScanType::kFieldValue; 476 | found_scan_type = true; 477 | break; 478 | case 'n': 479 | *type = ScanType::kPropertyName; 480 | found_scan_type = true; 481 | break; 482 | case 's': 483 | *type = ScanType::kStringValue; 484 | found_scan_type = true; 485 | break; 486 | default: 487 | *type = ScanType::kBadOption; 488 | break; 489 | } 490 | } while (true); 491 | 492 | return cmd + optind - 1; 493 | } 494 | 495 | 496 | void FindReferencesCmd::ReferenceScanner::PrintRefs( 497 | SBCommandReturnObject& result, v8::JSObject& js_obj, v8::Error& err) { 498 | int64_t length = js_obj.GetArrayLength(err); 499 | for (int64_t i = 0; i < length; ++i) { 500 | v8::Value v = js_obj.GetArrayElement(i, err); 501 | 502 | // Array is borked, or not array at all - skip it 503 | if (!err.Success()) break; 504 | 505 | if (v.raw() != search_value_.raw()) continue; 506 | 507 | std::string type_name = js_obj.GetTypeName(err); 508 | result.Printf("0x%" PRIx64 ": %s[%" PRId64 "]=0x%" PRIx64 "\n", 509 | js_obj.raw(), type_name.c_str(), i, search_value_.raw()); 510 | } 511 | 512 | // Walk all the properties in this object. 513 | // We only create strings for the field names that match the search 514 | // value. 515 | std::vector> entries = js_obj.Entries(err); 516 | if (err.Fail()) { 517 | return; 518 | } 519 | for (auto entry : entries) { 520 | v8::Value v = entry.second; 521 | if (v.raw() == search_value_.raw()) { 522 | std::string key = entry.first.ToString(err); 523 | std::string type_name = js_obj.GetTypeName(err); 524 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", js_obj.raw(), 525 | type_name.c_str(), key.c_str(), search_value_.raw()); 526 | } 527 | } 528 | } 529 | 530 | 531 | void FindReferencesCmd::ReferenceScanner::PrintRefs( 532 | SBCommandReturnObject& result, v8::String& str, v8::Error& err) { 533 | v8::LLV8* v8 = str.v8(); 534 | 535 | int64_t repr = str.Representation(err); 536 | 537 | // Concatenated and sliced strings refer to other strings so 538 | // we need to check their references. 539 | 540 | if (repr == v8->string()->kSlicedStringTag) { 541 | v8::SlicedString sliced_str(str); 542 | v8::String parent = sliced_str.Parent(err); 543 | if (err.Success() && parent.raw() == search_value_.raw()) { 544 | std::string type_name = sliced_str.GetTypeName(err); 545 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", str.raw(), 546 | type_name.c_str(), "", search_value_.raw()); 547 | } 548 | } else if (repr == v8->string()->kConsStringTag) { 549 | v8::ConsString cons_str(str); 550 | 551 | v8::String first = cons_str.First(err); 552 | if (err.Success() && first.raw() == search_value_.raw()) { 553 | std::string type_name = cons_str.GetTypeName(err); 554 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", str.raw(), 555 | type_name.c_str(), "", search_value_.raw()); 556 | } 557 | 558 | v8::String second = cons_str.Second(err); 559 | if (err.Success() && second.raw() == search_value_.raw()) { 560 | std::string type_name = cons_str.GetTypeName(err); 561 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", str.raw(), 562 | type_name.c_str(), "", search_value_.raw()); 563 | } 564 | } 565 | // Nothing to do for other kinds of string. 566 | } 567 | 568 | 569 | void FindReferencesCmd::PropertyScanner::PrintRefs( 570 | SBCommandReturnObject& result, v8::JSObject& js_obj, v8::Error& err) { 571 | 572 | // (Note: We skip array elements as they don't have names.) 573 | 574 | // Walk all the properties in this object. 575 | // We only create strings for the field names that match the search 576 | // value. 577 | std::vector> entries = js_obj.Entries(err); 578 | if (err.Fail()) { 579 | return; 580 | } 581 | for (auto entry : entries) { 582 | v8::HeapObject nameObj(entry.first); 583 | std::string key = entry.first.ToString(err); 584 | if (err.Fail()) { 585 | continue; 586 | } 587 | if (key == search_value_) { 588 | std::string type_name = js_obj.GetTypeName(err); 589 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 "\n", js_obj.raw(), 590 | type_name.c_str(), key.c_str(), entry.second.raw()); 591 | } 592 | } 593 | } 594 | 595 | 596 | void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, 597 | v8::JSObject& js_obj, 598 | v8::Error& err) { 599 | v8::LLV8* v8 = js_obj.v8(); 600 | 601 | int64_t length = js_obj.GetArrayLength(err); 602 | for (int64_t i = 0; i < length; ++i) { 603 | v8::Value v = js_obj.GetArrayElement(i, err); 604 | if (err.Fail()) { 605 | continue; 606 | } 607 | v8::HeapObject valueObj(v); 608 | 609 | int64_t type = valueObj.GetType(err); 610 | if (err.Fail()) { 611 | continue; 612 | } 613 | if (type < v8->types()->kFirstNonstringType) { 614 | v8::String valueString(valueObj); 615 | std::string value = valueString.ToString(err); 616 | if (err.Fail()) { 617 | continue; 618 | } 619 | if (err.Success() && search_value_ == value) { 620 | std::string type_name = js_obj.GetTypeName(err); 621 | 622 | result.Printf("0x%" PRIx64 ": %s[%" PRId64 "]=0x%" PRIx64 " '%s'\n", 623 | js_obj.raw(), type_name.c_str(), i, v.raw(), 624 | value.c_str()); 625 | } 626 | } 627 | } 628 | 629 | // Walk all the properties in this object. 630 | // We only create strings for the field names that match the search 631 | // value. 632 | std::vector> entries = js_obj.Entries(err); 633 | if (err.Success()) { 634 | for (auto entry : entries) { 635 | v8::HeapObject valueObj(entry.second); 636 | int64_t type = valueObj.GetType(err); 637 | if (err.Fail()) { 638 | continue; 639 | } 640 | if (type < v8->types()->kFirstNonstringType) { 641 | v8::String valueString(valueObj); 642 | std::string value = valueString.ToString(err); 643 | if (err.Fail()) { 644 | continue; 645 | } 646 | if (search_value_ == value) { 647 | std::string key = entry.first.ToString(err); 648 | if (err.Fail()) { 649 | continue; 650 | } 651 | std::string type_name = js_obj.GetTypeName(err); 652 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 " '%s'\n", 653 | js_obj.raw(), type_name.c_str(), key.c_str(), 654 | entry.second.raw(), value.c_str()); 655 | } 656 | } 657 | } 658 | } 659 | } 660 | 661 | 662 | void FindReferencesCmd::StringScanner::PrintRefs(SBCommandReturnObject& result, 663 | v8::String& str, 664 | v8::Error& err) { 665 | v8::LLV8* v8 = str.v8(); 666 | 667 | // Concatenated and sliced strings refer to other strings so 668 | // we need to check their references. 669 | 670 | int64_t repr = str.Representation(err); 671 | if (err.Fail()) return; 672 | 673 | if (repr == v8->string()->kSlicedStringTag) { 674 | v8::SlicedString sliced_str(str); 675 | v8::String parent_str = sliced_str.Parent(err); 676 | if (err.Fail()) return; 677 | std::string parent = parent_str.ToString(err); 678 | if (err.Success() && search_value_ == parent) { 679 | std::string type_name = sliced_str.GetTypeName(err); 680 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 " '%s'\n", str.raw(), 681 | type_name.c_str(), "", parent_str.raw(), 682 | parent.c_str()); 683 | } 684 | } else if (repr == v8->string()->kConsStringTag) { 685 | v8::ConsString cons_str(str); 686 | 687 | v8::String first_str = cons_str.First(err); 688 | if (err.Fail()) return; 689 | 690 | // It looks like sometimes one of the strings can be or another 691 | // value, 692 | // verify that they are a JavaScript String before calling ToString. 693 | int64_t first_type = first_str.GetType(err); 694 | if (err.Fail()) return; 695 | 696 | if (first_type < v8->types()->kFirstNonstringType) { 697 | std::string first = first_str.ToString(err); 698 | 699 | if (err.Success() && search_value_ == first) { 700 | std::string type_name = cons_str.GetTypeName(err); 701 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 " '%s'\n", str.raw(), 702 | type_name.c_str(), "", first_str.raw(), 703 | first.c_str()); 704 | } 705 | } 706 | 707 | v8::String second_str = cons_str.Second(err); 708 | if (err.Fail()) return; 709 | 710 | // It looks like sometimes one of the strings can be or another 711 | // value, 712 | // verify that they are a JavaScript String before calling ToString. 713 | int64_t second_type = second_str.GetType(err); 714 | if (err.Fail()) return; 715 | 716 | if (second_type < v8->types()->kFirstNonstringType) { 717 | std::string second = second_str.ToString(err); 718 | 719 | if (err.Success() && search_value_ == second) { 720 | std::string type_name = cons_str.GetTypeName(err); 721 | result.Printf("0x%" PRIx64 ": %s.%s=0x%" PRIx64 " '%s'\n", str.raw(), 722 | type_name.c_str(), "", second_str.raw(), 723 | second.c_str()); 724 | } 725 | } 726 | } 727 | // Nothing to do for other kinds of string. 728 | // They are strings so we will find references to them. 729 | } 730 | 731 | 732 | FindJSObjectsVisitor::FindJSObjectsVisitor(SBTarget& target, 733 | TypeRecordMap& mapstoinstances) 734 | : target_(target), mapstoinstances_(mapstoinstances) { 735 | found_count_ = 0; 736 | address_byte_size_ = target_.GetProcess().GetAddressByteSize(); 737 | // Load V8 constants from postmortem data 738 | llv8.Load(target); 739 | } 740 | 741 | 742 | /* Visit every address, a bit brute force but it works. */ 743 | uint64_t FindJSObjectsVisitor::Visit(uint64_t location, uint64_t word) { 744 | v8::Value v8_value(&llv8, word); 745 | 746 | v8::Error err; 747 | // Test if this is SMI 748 | // Skip inspecting things that look like Smi's, they aren't objects. 749 | v8::Smi smi(v8_value); 750 | if (smi.Check()) return address_byte_size_; 751 | 752 | v8::HeapObject heap_object(v8_value); 753 | if (!heap_object.Check()) return address_byte_size_; 754 | 755 | v8::HeapObject map_object = heap_object.GetMap(err); 756 | if (err.Fail() || !map_object.Check()) return address_byte_size_; 757 | 758 | v8::Map map(map_object); 759 | 760 | MapCacheEntry map_info; 761 | if (map_cache_.count(map.raw()) == 0) { 762 | 763 | // Check type first 764 | map_info.is_histogram = IsAHistogramType(map, err); 765 | 766 | // On success load type name 767 | if (map_info.is_histogram) 768 | map_info.type_name = heap_object.GetTypeName(err); 769 | 770 | // Cache result 771 | map_cache_.emplace(map.raw(), map_info); 772 | 773 | if (err.Fail()) return address_byte_size_; 774 | } else { 775 | map_info = map_cache_.at(map.raw()); 776 | } 777 | 778 | if (!map_info.is_histogram) return address_byte_size_; 779 | 780 | /* No entry in the map, create a new one. */ 781 | if (mapstoinstances_.count(map_info.type_name) == 0) { 782 | TypeRecord* t = new TypeRecord(map_info.type_name); 783 | 784 | t->AddInstance(word, map.InstanceSize(err)); 785 | mapstoinstances_.emplace(map_info.type_name, t); 786 | 787 | } else { 788 | /* Update an existing instance, if we haven't seen this instance before. */ 789 | TypeRecord* t = mapstoinstances_.at(map_info.type_name); 790 | /* Determine if this is a new instance. 791 | * (We are scanning pointers to objects, we may have seen this location 792 | * before.) 793 | */ 794 | if (t->GetInstances().count(word) == 0) { 795 | t->AddInstance(word, map.InstanceSize(err)); 796 | } 797 | } 798 | 799 | if (err.Fail()) { 800 | return address_byte_size_; 801 | } 802 | 803 | found_count_++; 804 | 805 | /* Just advance one word. 806 | * (Should advance by object size, assuming objects can't overlap!) 807 | */ 808 | return address_byte_size_; 809 | } 810 | 811 | 812 | bool FindJSObjectsVisitor::IsAHistogramType(v8::Map& map, v8::Error& err) { 813 | int64_t type = map.GetType(err); 814 | if (err.Fail()) return false; 815 | 816 | v8::LLV8* v8 = map.v8(); 817 | if (v8::JSObject::IsObjectType(v8, type)) return true; 818 | if (type == v8->types()->kJSArrayType) return true; 819 | if (type == v8->types()->kJSTypedArrayType) return true; 820 | if (type < v8->types()->kFirstNonstringType) return true; 821 | return false; 822 | } 823 | 824 | 825 | bool LLScan::ScanHeapForObjects(lldb::SBTarget target, 826 | lldb::SBCommandReturnObject& result) { 827 | /* Check the last scan is still valid - the process hasn't moved 828 | * and we haven't changed target. 829 | */ 830 | 831 | // Reload process anyway 832 | process_ = target.GetProcess(); 833 | 834 | // Need to reload memory ranges (though this does assume the user has also 835 | // updated 836 | // LLNODE_RANGESFILE with data for the new dump or things won't match up). 837 | if (target_ != target) { 838 | ClearMemoryRanges(); 839 | mapstoinstances_.clear(); 840 | target_ = target; 841 | } 842 | 843 | #ifndef LLDB_SBMemoryRegionInfoList_h_ 844 | /* Fall back to environment variable containing pre-parsed list of memory 845 | * ranges. */ 846 | if (nullptr == ranges_) { 847 | const char* segmentsfilename = getenv("LLNODE_RANGESFILE"); 848 | 849 | if (segmentsfilename == nullptr) { 850 | result.SetError( 851 | "No memory range information available for this process. Cannot scan " 852 | "for objects.\n" 853 | "Please set `LLNODE_RANGESFILE` environment variable\n"); 854 | return false; 855 | } 856 | 857 | if (!GenerateMemoryRanges(target, segmentsfilename)) { 858 | result.SetError( 859 | "No memory range information available for this process. Cannot scan " 860 | "for objects.\n"); 861 | return false; 862 | } 863 | } 864 | #endif // LLDB_SBMemoryRegionInfoList_h_ 865 | 866 | /* If we've reached here we have access to information about the valid memory 867 | * ranges in the process and can scan for objects. 868 | */ 869 | 870 | /* Populate the map of objects. */ 871 | if (mapstoinstances_.empty()) { 872 | FindJSObjectsVisitor v(target, GetMapsToInstances()); 873 | 874 | ScanMemoryRanges(v); 875 | } 876 | 877 | return true; 878 | } 879 | 880 | 881 | void LLScan::ScanMemoryRanges(FindJSObjectsVisitor& v) { 882 | bool done = false; 883 | 884 | const uint64_t addr_size = process_.GetAddressByteSize(); 885 | 886 | // Pages are usually around 1mb, so this should more than enough 887 | const uint64_t block_size = 1024 * 1024 * addr_size; 888 | unsigned char* block = new unsigned char[block_size]; 889 | 890 | #ifndef LLDB_SBMemoryRegionInfoList_h_ 891 | MemoryRange* head = ranges_; 892 | 893 | while (head != nullptr && !done) { 894 | uint64_t address = head->start_; 895 | uint64_t len = head->length_; 896 | head = head->next_; 897 | 898 | #else // LLDB_SBMemoryRegionInfoList_h_ 899 | SBMemoryRegionInfoList memory_regions = process_.GetMemoryRegions(); 900 | SBMemoryRegionInfo region_info; 901 | 902 | for (uint32_t i = 0; i < memory_regions.GetSize(); ++i) { 903 | memory_regions.GetMemoryRegionAtIndex(i, region_info); 904 | 905 | if (!region_info.IsWritable()) { 906 | continue; 907 | } 908 | 909 | uint64_t address = region_info.GetRegionBase(); 910 | uint64_t len = region_info.GetRegionEnd() - region_info.GetRegionBase(); 911 | 912 | #endif // LLDB_SBMemoryRegionInfoList_h_ 913 | /* Brute force search - query every address - but allow the visitor code to 914 | * say how far to move on so we don't read every byte. 915 | */ 916 | 917 | SBError sberr; 918 | uint64_t address_end = address + len; 919 | 920 | // Load data in blocks to speed up whole process 921 | for (auto searchAddress = address; searchAddress < address_end; 922 | searchAddress += block_size) { 923 | size_t loaded = std::min(address_end - searchAddress, block_size); 924 | process_.ReadMemory(searchAddress, block, loaded, sberr); 925 | if (sberr.Fail()) { 926 | // TODO(indutny): add error information 927 | break; 928 | } 929 | 930 | uint32_t increment = 1; 931 | for (size_t j = 0; j + addr_size <= loaded;) { 932 | uint64_t value; 933 | 934 | if (addr_size == 4) 935 | value = *reinterpret_cast(&block[j]); 936 | else if (addr_size == 8) 937 | value = *reinterpret_cast(&block[j]); 938 | else 939 | break; 940 | 941 | increment = v.Visit(j + searchAddress, value); 942 | if (increment == 0) break; 943 | 944 | j += static_cast(increment); 945 | } 946 | 947 | if (increment == 0) { 948 | done = true; 949 | break; 950 | } 951 | } 952 | } 953 | 954 | delete[] block; 955 | } 956 | 957 | 958 | /* Read a file of memory ranges parsed from the core dump. 959 | * This is a work around for the lack of an API to get the memory ranges 960 | * within lldb. 961 | * There are scripts for generating this file on Mac and Linux stored in 962 | * the scripts directory of the llnode repository. 963 | * Export the name or full path to the ranges file in the LLNODE_RANGESFILE 964 | * env var before starting lldb and loading the llnode plugin. 965 | */ 966 | bool LLScan::GenerateMemoryRanges(lldb::SBTarget target, 967 | const char* segmentsfilename) { 968 | std::ifstream input(segmentsfilename); 969 | 970 | if (!input.is_open()) { 971 | return false; 972 | } 973 | 974 | uint64_t address = 0; 975 | uint64_t len = 0; 976 | 977 | MemoryRange** tailptr = &ranges_; 978 | 979 | lldb::addr_t address_byte_size = target.GetProcess().GetAddressByteSize(); 980 | 981 | while (input >> std::hex >> address >> std::hex >> len) { 982 | /* Check if the range is accessible. 983 | * The structure of a core file means if you check the start and the end of 984 | * a range then the middle will be there, ranges are contiguous in the file, 985 | * but cores often get truncated due to file size limits so ranges can be 986 | * missing or truncated. Sometimes shared memory segments are omitted so 987 | * it's also possible an entire section could be missing from the middle. 988 | */ 989 | lldb::SBError error; 990 | 991 | target.GetProcess().ReadPointerFromMemory(address, error); 992 | if (!error.Success()) { 993 | /* Could not access first word, skip. */ 994 | continue; 995 | } 996 | 997 | target.GetProcess().ReadPointerFromMemory( 998 | (address + len) - address_byte_size, error); 999 | if (!error.Success()) { 1000 | /* Could not access last word, skip. */ 1001 | continue; 1002 | } 1003 | 1004 | MemoryRange* newRange = new MemoryRange(address, len); 1005 | 1006 | *tailptr = newRange; 1007 | tailptr = &(newRange->next_); 1008 | } 1009 | return true; 1010 | } 1011 | 1012 | 1013 | void LLScan::ClearMemoryRanges() { 1014 | MemoryRange* head = ranges_; 1015 | while (head != nullptr) { 1016 | MemoryRange* range = head; 1017 | head = head->next_; 1018 | delete range; 1019 | } 1020 | ranges_ = nullptr; 1021 | } 1022 | 1023 | 1024 | void LLScan::ClearMapsToInstances() { 1025 | TypeRecordMap::iterator end = GetMapsToInstances().end(); 1026 | for (TypeRecordMap::iterator it = GetMapsToInstances().begin(); it != end; 1027 | ++it) { 1028 | TypeRecord* t = it->second; 1029 | delete t; 1030 | } 1031 | GetMapsToInstances().clear(); 1032 | } 1033 | } 1034 | -------------------------------------------------------------------------------- /src/llscan.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LLSCAN_H_ 2 | #define SRC_LLSCAN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace llnode { 9 | 10 | class FindObjectsCmd : public CommandBase { 11 | public: 12 | ~FindObjectsCmd() override {} 13 | 14 | bool DoExecute(lldb::SBDebugger d, char** cmd, 15 | lldb::SBCommandReturnObject& result) override; 16 | }; 17 | 18 | class FindInstancesCmd : public CommandBase { 19 | public: 20 | ~FindInstancesCmd() override {} 21 | 22 | bool DoExecute(lldb::SBDebugger d, char** cmd, 23 | lldb::SBCommandReturnObject& result) override; 24 | 25 | private: 26 | bool detailed_; 27 | }; 28 | 29 | class NodeInfoCmd : public CommandBase { 30 | public: 31 | ~NodeInfoCmd() override {} 32 | 33 | bool DoExecute(lldb::SBDebugger d, char** cmd, 34 | lldb::SBCommandReturnObject& result) override; 35 | }; 36 | 37 | class FindReferencesCmd : public CommandBase { 38 | public: 39 | ~FindReferencesCmd() override {} 40 | 41 | bool DoExecute(lldb::SBDebugger d, char** cmd, 42 | lldb::SBCommandReturnObject& result) override; 43 | 44 | enum ScanType { kFieldValue, kPropertyName, kStringValue, kBadOption }; 45 | 46 | char** ParseScanOptions(char** cmd, ScanType* type); 47 | 48 | class ObjectScanner { 49 | public: 50 | virtual ~ObjectScanner() {} 51 | virtual void PrintRefs(lldb::SBCommandReturnObject& result, 52 | v8::JSObject& js_obj, v8::Error& err) {} 53 | virtual void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, 54 | v8::Error& err) {} 55 | }; 56 | 57 | class ReferenceScanner : public ObjectScanner { 58 | public: 59 | ReferenceScanner(v8::Value& search_value) : search_value_(search_value) {} 60 | 61 | void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, 62 | v8::Error& err) override; 63 | void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, 64 | v8::Error& err) override; 65 | 66 | private: 67 | v8::Value& search_value_; 68 | }; 69 | 70 | 71 | class PropertyScanner : public ObjectScanner { 72 | public: 73 | PropertyScanner(std::string search_value) : search_value_(search_value) {} 74 | 75 | // We only scan properties on objects not Strings, use default no-op impl 76 | // of PrintRefs for Strings. 77 | void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, 78 | v8::Error& err) override; 79 | 80 | private: 81 | std::string search_value_; 82 | }; 83 | 84 | 85 | class StringScanner : public ObjectScanner { 86 | public: 87 | StringScanner(std::string search_value) : search_value_(search_value) {} 88 | 89 | void PrintRefs(lldb::SBCommandReturnObject& result, v8::JSObject& js_obj, 90 | v8::Error& err) override; 91 | void PrintRefs(lldb::SBCommandReturnObject& result, v8::String& str, 92 | v8::Error& err) override; 93 | 94 | private: 95 | std::string search_value_; 96 | }; 97 | }; 98 | 99 | class MemoryVisitor { 100 | public: 101 | virtual ~MemoryVisitor() {} 102 | 103 | virtual uint64_t Visit(uint64_t location, uint64_t available) = 0; 104 | }; 105 | 106 | class TypeRecord { 107 | public: 108 | TypeRecord(std::string& type_name) 109 | : type_name_(type_name), instance_count_(0), total_instance_size_(0) {} 110 | 111 | inline std::string& GetTypeName() { return type_name_; }; 112 | inline uint64_t GetInstanceCount() { return instance_count_; }; 113 | inline uint64_t GetTotalInstanceSize() { return total_instance_size_; }; 114 | inline std::set& GetInstances() { return instances_; }; 115 | 116 | inline void AddInstance(uint64_t address, uint64_t size) { 117 | instances_.insert(address); 118 | instance_count_++; 119 | total_instance_size_ += size; 120 | }; 121 | 122 | /* Sort records by instance count, use the other fields as tie breakers 123 | * to give consistent ordering. 124 | */ 125 | static bool CompareInstanceCounts(TypeRecord* a, TypeRecord* b) { 126 | if (a->instance_count_ == b->instance_count_) { 127 | if (a->total_instance_size_ == b->total_instance_size_) { 128 | return a->type_name_ < b->type_name_; 129 | } 130 | return a->total_instance_size_ < b->total_instance_size_; 131 | } 132 | return a->instance_count_ < b->instance_count_; 133 | } 134 | 135 | 136 | private: 137 | std::string type_name_; 138 | uint64_t instance_count_; 139 | uint64_t total_instance_size_; 140 | std::set instances_; 141 | }; 142 | 143 | typedef std::map TypeRecordMap; 144 | 145 | class FindJSObjectsVisitor : MemoryVisitor { 146 | public: 147 | FindJSObjectsVisitor(lldb::SBTarget& target, TypeRecordMap& mapstoinstances); 148 | ~FindJSObjectsVisitor() {} 149 | 150 | uint64_t Visit(uint64_t location, uint64_t word); 151 | 152 | uint32_t FoundCount() { return found_count_; } 153 | 154 | private: 155 | struct MapCacheEntry { 156 | std::string type_name; 157 | bool is_histogram; 158 | }; 159 | 160 | bool IsAHistogramType(v8::Map& map, v8::Error& err); 161 | 162 | lldb::SBTarget& target_; 163 | uint32_t address_byte_size_; 164 | uint32_t found_count_; 165 | 166 | TypeRecordMap& mapstoinstances_; 167 | std::map map_cache_; 168 | }; 169 | 170 | 171 | class LLScan { 172 | public: 173 | LLScan() {} 174 | 175 | bool ScanHeapForObjects(lldb::SBTarget target, 176 | lldb::SBCommandReturnObject& result); 177 | bool GenerateMemoryRanges(lldb::SBTarget target, 178 | const char* segmentsfilename); 179 | 180 | inline TypeRecordMap& GetMapsToInstances() { return mapstoinstances_; }; 181 | 182 | private: 183 | void ScanMemoryRanges(FindJSObjectsVisitor& v); 184 | void ClearMemoryRanges(); 185 | void ClearMapsToInstances(); 186 | 187 | class MemoryRange { 188 | public: 189 | MemoryRange(uint64_t start, uint64_t length) 190 | : start_(start), length_(length), next_(nullptr) {} 191 | 192 | uint64_t start_; 193 | uint64_t length_; 194 | MemoryRange* next_; 195 | }; 196 | 197 | lldb::SBTarget target_; 198 | lldb::SBProcess process_; 199 | MemoryRange* ranges_ = nullptr; 200 | TypeRecordMap mapstoinstances_; 201 | }; 202 | 203 | } // llnode 204 | 205 | 206 | #endif // SRC_LLSCAN_H_ 207 | -------------------------------------------------------------------------------- /src/llv8-constants.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "src/llv8-constants.h" 9 | #include "src/llv8.h" 10 | 11 | namespace llnode { 12 | namespace v8 { 13 | namespace constants { 14 | 15 | using namespace lldb; 16 | 17 | static std::string kConstantPrefix = "v8dbg_"; 18 | 19 | static bool IsDebugMode() { 20 | char* var = getenv("LLNODE_DEBUG"); 21 | if (var == nullptr) return false; 22 | 23 | return strlen(var) != 0; 24 | } 25 | 26 | 27 | void Module::Assign(SBTarget target, Common* common) { 28 | loaded_ = false; 29 | target_ = target; 30 | common_ = common; 31 | } 32 | 33 | 34 | static int64_t LookupConstant(SBTarget target, const char* name, int64_t def, 35 | Error& err) { 36 | int64_t res; 37 | 38 | res = def; 39 | 40 | SBSymbolContextList context_list = target.FindSymbols(name); 41 | if (!context_list.IsValid() || context_list.GetSize() == 0) { 42 | err = Error::Failure("Failed to find symbol"); 43 | return res; 44 | } 45 | 46 | SBSymbolContext context = context_list.GetContextAtIndex(0); 47 | SBSymbol symbol = context.GetSymbol(); 48 | if (!symbol.IsValid()) { 49 | err = Error::Failure("Failed to fetch symbol"); 50 | return res; 51 | } 52 | 53 | SBAddress start = symbol.GetStartAddress(); 54 | SBAddress end = symbol.GetEndAddress(); 55 | size_t size = end.GetOffset() - start.GetOffset(); 56 | 57 | SBError sberr; 58 | 59 | SBProcess process = target.GetProcess(); 60 | addr_t addr = start.GetFileAddress(); 61 | 62 | // NOTE: size could be bigger for at the end symbols 63 | if (size >= 8) { 64 | process.ReadMemory(addr, &res, 8, sberr); 65 | } else if (size == 4) { 66 | int32_t tmp; 67 | process.ReadMemory(addr, &tmp, size, sberr); 68 | res = static_cast(tmp); 69 | } else if (size == 2) { 70 | int16_t tmp; 71 | process.ReadMemory(addr, &tmp, size, sberr); 72 | res = static_cast(tmp); 73 | } else if (size == 1) { 74 | int8_t tmp; 75 | process.ReadMemory(addr, &tmp, size, sberr); 76 | res = static_cast(tmp); 77 | } else { 78 | err = Error::Failure("Unexpected symbol size"); 79 | return res; 80 | } 81 | 82 | if (sberr.Fail()) 83 | err = Error::Failure("Failed to load symbol"); 84 | else 85 | err = Error::Ok(); 86 | 87 | return res; 88 | } 89 | 90 | 91 | int64_t Module::LoadRawConstant(const char* name, int64_t def) { 92 | Error err; 93 | int64_t v = LookupConstant(target_, name, def, err); 94 | if (err.Fail() && IsDebugMode()) fprintf(stderr, "Failed to load %s\n", name); 95 | 96 | return v; 97 | } 98 | 99 | 100 | int64_t Module::LoadConstant(const char* name, int64_t def) { 101 | Error err; 102 | int64_t v = 103 | LookupConstant(target_, (kConstantPrefix + name).c_str(), def, err); 104 | if (err.Fail() && IsDebugMode()) fprintf(stderr, "Failed to load %s\n", name); 105 | 106 | return v; 107 | } 108 | 109 | 110 | int64_t Module::LoadConstant(const char* name, const char* fallback, 111 | int64_t def) { 112 | Error err; 113 | int64_t v = 114 | LookupConstant(target_, (kConstantPrefix + name).c_str(), def, err); 115 | if (err.Fail()) 116 | v = LookupConstant(target_, (kConstantPrefix + fallback).c_str(), def, err); 117 | if (err.Fail() && IsDebugMode()) fprintf(stderr, "Failed to load %s\n", name); 118 | 119 | return v; 120 | } 121 | 122 | 123 | void Common::Load() { 124 | kPointerSize = 1 << LoadConstant("PointerSizeLog2"); 125 | kVersionMajor = LoadRawConstant("v8::internal::Version::major_"); 126 | kVersionMinor = LoadRawConstant("v8::internal::Version::minor_"); 127 | kVersionPatch = LoadRawConstant("v8::internal::Version::patch_"); 128 | } 129 | 130 | 131 | bool Common::CheckHighestVersion(int64_t major, int64_t minor, int64_t patch) { 132 | Load(); 133 | 134 | if (kVersionMajor < major) return true; 135 | if (kVersionMajor > major) return false; 136 | 137 | if (kVersionMinor < minor) return true; 138 | if (kVersionMinor > minor) return false; 139 | 140 | if (kVersionPatch > patch) return false; 141 | return true; 142 | } 143 | 144 | 145 | bool Common::CheckLowestVersion(int64_t major, int64_t minor, int64_t patch) { 146 | Load(); 147 | 148 | if (kVersionMajor > major) return true; 149 | if (kVersionMajor < major) return false; 150 | 151 | if (kVersionMinor > minor) return true; 152 | if (kVersionMinor < minor) return false; 153 | 154 | if (kVersionPatch < patch) return false; 155 | return true; 156 | } 157 | 158 | 159 | void Smi::Load() { 160 | kTag = LoadConstant("SmiTag"); 161 | kTagMask = LoadConstant("SmiTagMask"); 162 | kShiftSize = LoadConstant("SmiShiftSize"); 163 | } 164 | 165 | 166 | void HeapObject::Load() { 167 | kTag = LoadConstant("HeapObjectTag"); 168 | kTagMask = LoadConstant("HeapObjectTagMask"); 169 | kMapOffset = LoadConstant("class_HeapObject__map__Map"); 170 | } 171 | 172 | 173 | void Map::Load() { 174 | kInstanceAttrsOffset = LoadConstant("class_Map__instance_attributes__int"); 175 | kMaybeConstructorOffset = 176 | LoadConstant("class_Map__constructor_or_backpointer__Object", 177 | "class_Map__constructor__Object"); 178 | kInstanceDescriptorsOffset = 179 | LoadConstant("class_Map__instance_descriptors__DescriptorArray"); 180 | kBitField3Offset = 181 | LoadConstant("class_Map__bit_field3__int", "class_Map__bit_field3__SMI"); 182 | kInObjectPropertiesOffset = LoadConstant( 183 | "class_Map__inobject_properties_or_constructor_function_index__int", 184 | "class_Map__inobject_properties__int"); 185 | kInstanceSizeOffset = LoadConstant("class_Map__instance_size__int"); 186 | 187 | kDictionaryMapShift = LoadConstant("bit_field3_dictionary_map_shift"); 188 | 189 | kNumberOfOwnDescriptorsShift = 190 | LoadConstant("bit_field3_number_of_own_descriptors_shift"); 191 | kNumberOfOwnDescriptorsMask = 192 | LoadConstant("bit_field3_number_of_own_descriptors_mask"); 193 | 194 | if (kNumberOfOwnDescriptorsShift == -1) { 195 | // TODO(indutny): check v8 version? 196 | static const int64_t kDescriptorIndexBitCount = 10; 197 | 198 | kNumberOfOwnDescriptorsShift = kDictionaryMapShift; 199 | kNumberOfOwnDescriptorsShift -= kDescriptorIndexBitCount; 200 | 201 | kNumberOfOwnDescriptorsMask = (1 << kDescriptorIndexBitCount) - 1; 202 | kNumberOfOwnDescriptorsMask <<= kNumberOfOwnDescriptorsShift; 203 | } 204 | } 205 | 206 | 207 | void JSObject::Load() { 208 | kPropertiesOffset = LoadConstant("class_JSReceiver__properties__FixedArray", 209 | "class_JSObject__properties__FixedArray"); 210 | kElementsOffset = LoadConstant("class_JSObject__elements__Object"); 211 | kInternalFieldsOffset = 212 | LoadConstant("class_JSObject__internal_fields__uintptr_t"); 213 | 214 | if (kInternalFieldsOffset == -1) { 215 | common_->Load(); 216 | 217 | // TODO(indutny): check V8 version? 218 | kInternalFieldsOffset = kElementsOffset + common_->kPointerSize; 219 | } 220 | } 221 | 222 | 223 | void HeapNumber::Load() { 224 | kValueOffset = LoadConstant("class_HeapNumber__value__double"); 225 | } 226 | 227 | 228 | void JSArray::Load() { 229 | kLengthOffset = LoadConstant("class_JSArray__length__Object"); 230 | } 231 | 232 | 233 | void JSFunction::Load() { 234 | kSharedInfoOffset = 235 | LoadConstant("class_JSFunction__shared__SharedFunctionInfo"); 236 | kContextOffset = LoadConstant("class_JSFunction__context__Context"); 237 | 238 | if (kContextOffset == -1) { 239 | common_->Load(); 240 | 241 | // TODO(indutny): check V8 version? 242 | kContextOffset = kSharedInfoOffset + common_->kPointerSize; 243 | } 244 | } 245 | 246 | 247 | void JSRegExp::Load() { 248 | kSourceOffset = LoadConstant("class_JSRegExp__source__Object"); 249 | } 250 | 251 | 252 | void JSDate::Load() { 253 | kValueOffset = LoadConstant("class_JSDate__value__Object"); 254 | }; 255 | 256 | 257 | void SharedInfo::Load() { 258 | kNameOffset = LoadConstant("class_SharedFunctionInfo__name__Object"); 259 | kInferredNameOffset = 260 | LoadConstant("class_SharedFunctionInfo__inferred_name__String", 261 | "class_SharedFunctionInfo__function_identifier__Object"); 262 | kScriptOffset = LoadConstant("class_SharedFunctionInfo__script__Object"); 263 | kCodeOffset = LoadConstant("class_SharedFunctionInfo__code__Code"); 264 | kStartPositionOffset = 265 | LoadConstant("class_SharedFunctionInfo__start_position_and_type__SMI"); 266 | kEndPositionOffset = 267 | LoadConstant("class_SharedFunctionInfo__end_position__SMI"); 268 | kParameterCountOffset = LoadConstant( 269 | "class_SharedFunctionInfo__internal_formal_parameter_count__SMI", 270 | "class_SharedFunctionInfo__formal_parameter_count__SMI"); 271 | 272 | // NOTE: Could potentially be -1 on v4 and v5 node, should check in llv8 273 | kScopeInfoOffset = 274 | LoadConstant("class_SharedFunctionInfo__scope_info__ScopeInfo"); 275 | 276 | kStartPositionMask = LoadConstant("sharedfunctioninfo_start_position_mask"); 277 | kStartPositionShift = LoadConstant("sharedfunctioninfo_start_position_shift"); 278 | 279 | if (kStartPositionShift == -1) { 280 | // TODO(indutny): check version? 281 | kStartPositionShift = 2; 282 | kStartPositionMask = ~((1 << kStartPositionShift) - 1); 283 | } 284 | } 285 | 286 | 287 | void Code::Load() { 288 | kStartOffset = LoadConstant("class_Code__instruction_start__uintptr_t"); 289 | kSizeOffset = LoadConstant("class_Code__instruction_size__int"); 290 | } 291 | 292 | 293 | void ScopeInfo::Load() { 294 | kParameterCountOffset = LoadConstant("scopeinfo_idx_nparams"); 295 | kStackLocalCountOffset = LoadConstant("scopeinfo_idx_nstacklocals"); 296 | kContextLocalCountOffset = LoadConstant("scopeinfo_idx_ncontextlocals"); 297 | kContextGlobalCountOffset = LoadConstant("scopeinfo_idx_ncontextglobals"); 298 | kVariablePartIndex = LoadConstant("scopeinfo_idx_first_vars"); 299 | } 300 | 301 | 302 | void Context::Load() { 303 | kClosureIndex = 304 | LoadConstant("class_Context__closure_index__int", "context_idx_closure"); 305 | kPreviousIndex = 306 | LoadConstant("class_Context__previous_index__int", "context_idx_prev"); 307 | kMinContextSlots = LoadConstant("class_Context__min_context_slots__int", 308 | "context_min_slots"); 309 | } 310 | 311 | 312 | void Script::Load() { 313 | kNameOffset = LoadConstant("class_Script__name__Object"); 314 | kLineOffsetOffset = LoadConstant("class_Script__line_offset__SMI"); 315 | kSourceOffset = LoadConstant("class_Script__source__Object"); 316 | kLineEndsOffset = LoadConstant("class_Script__line_ends__Object"); 317 | } 318 | 319 | 320 | void String::Load() { 321 | kEncodingMask = LoadConstant("StringEncodingMask"); 322 | kRepresentationMask = LoadConstant("StringRepresentationMask"); 323 | 324 | kOneByteStringTag = LoadConstant("OneByteStringTag", "AsciiStringTag"); 325 | kTwoByteStringTag = LoadConstant("TwoByteStringTag"); 326 | kSeqStringTag = LoadConstant("SeqStringTag"); 327 | kConsStringTag = LoadConstant("ConsStringTag"); 328 | kSlicedStringTag = LoadConstant("SlicedStringTag"); 329 | kExternalStringTag = LoadConstant("ExternalStringTag"); 330 | 331 | kLengthOffset = LoadConstant("class_String__length__SMI"); 332 | } 333 | 334 | 335 | void OneByteString::Load() { 336 | kCharsOffset = LoadConstant("class_SeqOneByteString__chars__char", 337 | "class_SeqAsciiString__chars__char"); 338 | } 339 | 340 | 341 | void TwoByteString::Load() { 342 | kCharsOffset = LoadConstant("class_SeqTwoByteString__chars__char", 343 | "class_SeqAsciiString__chars__char"); 344 | } 345 | 346 | 347 | void ConsString::Load() { 348 | kFirstOffset = LoadConstant("class_ConsString__first__String"); 349 | kSecondOffset = LoadConstant("class_ConsString__second__String"); 350 | } 351 | 352 | 353 | void SlicedString::Load() { 354 | kParentOffset = LoadConstant("class_SlicedString__parent__String"); 355 | kOffsetOffset = LoadConstant("class_SlicedString__offset__SMI"); 356 | } 357 | 358 | 359 | void FixedArrayBase::Load() { 360 | kLengthOffset = LoadConstant("class_FixedArrayBase__length__SMI"); 361 | } 362 | 363 | 364 | void FixedArray::Load() { 365 | kDataOffset = LoadConstant("class_FixedArray__data__uintptr_t"); 366 | } 367 | 368 | 369 | void Oddball::Load() { 370 | kKindOffset = LoadConstant("class_Oddball__kind_offset__int"); 371 | 372 | kException = LoadConstant("OddballException"); 373 | kFalse = LoadConstant("OddballFalse"); 374 | kTrue = LoadConstant("OddballTrue"); 375 | kUndefined = LoadConstant("OddballUndefined"); 376 | kTheHole = LoadConstant("OddballTheHole"); 377 | kNull = LoadConstant("OddballNull"); 378 | kUninitialized = LoadConstant("OddballUninitialized"); 379 | } 380 | 381 | 382 | void JSArrayBuffer::Load() { 383 | kBackingStoreOffset = 384 | LoadConstant("class_JSArrayBuffer__backing_store__Object"); 385 | kByteLengthOffset = LoadConstant("class_JSArrayBuffer__byte_length__Object"); 386 | 387 | // v4 compatibility fix 388 | if (kBackingStoreOffset == -1) { 389 | common_->Load(); 390 | 391 | kBackingStoreOffset = kByteLengthOffset + common_->kPointerSize; 392 | } 393 | 394 | kBitFieldOffset = kBackingStoreOffset + common_->kPointerSize; 395 | if (common_->kPointerSize == 8) kBitFieldOffset += 4; 396 | 397 | kWasNeuteredMask = LoadConstant("jsarray_buffer_was_neutered_mask"); 398 | kWasNeuteredShift = LoadConstant("jsarray_buffer_was_neutered_shift"); 399 | 400 | if (kWasNeuteredMask == -1) { 401 | // TODO(indutny): check V8 version? 402 | kWasNeuteredMask = 1 << 3; 403 | kWasNeuteredShift = 3; 404 | } 405 | } 406 | 407 | 408 | void JSArrayBufferView::Load() { 409 | kBufferOffset = LoadConstant("class_JSArrayBufferView__buffer__Object"); 410 | kByteOffsetOffset = 411 | LoadConstant("class_JSArrayBufferView__raw_byte_offset__Object"); 412 | kByteLengthOffset = 413 | LoadConstant("class_JSArrayBufferView__raw_byte_length__Object"); 414 | } 415 | 416 | 417 | void DescriptorArray::Load() { 418 | kDetailsOffset = LoadConstant("prop_desc_details"); 419 | kKeyOffset = LoadConstant("prop_desc_key"); 420 | kValueOffset = LoadConstant("prop_desc_value"); 421 | 422 | kPropertyIndexMask = LoadConstant("prop_index_mask"); 423 | kPropertyIndexShift = LoadConstant("prop_index_shift"); 424 | kPropertyTypeMask = LoadConstant("prop_type_mask"); 425 | 426 | kRepresentationShift = LoadConstant("prop_representation_shift"); 427 | kRepresentationMask = LoadConstant("prop_representation_mask"); 428 | 429 | if (kRepresentationShift == -1) { 430 | // TODO(indutny): check V8 version? 431 | kRepresentationShift = 5; 432 | kRepresentationMask = ((1 << 4) - 1) << kRepresentationShift; 433 | } 434 | 435 | kFieldType = LoadConstant("prop_type_field"); 436 | kConstFieldType = LoadConstant("prop_type_const_field"); 437 | if (kConstFieldType == -1) { 438 | // TODO(indutny): check V8 version? 439 | kConstFieldType = kFieldType | 0x2; 440 | } 441 | 442 | kRepresentationDouble = LoadConstant("prop_representation_double"); 443 | if (kRepresentationDouble == -1) { 444 | // TODO(indutny): check V8 version? 445 | kRepresentationDouble = 7; 446 | } 447 | 448 | kFirstIndex = LoadConstant("prop_idx_first"); 449 | kSize = LoadConstant("prop_desc_size"); 450 | } 451 | 452 | 453 | void NameDictionary::Load() { 454 | // TODO(indutny): move this to postmortem 455 | kKeyOffset = 0; 456 | kValueOffset = 1; 457 | 458 | kEntrySize = LoadConstant("class_NameDictionaryShape__entry_size__int", 459 | "namedictionaryshape_entry_size"); 460 | 461 | kPrefixStartIndex = 462 | LoadConstant("class_NameDictionary__prefix_start_index__int", 463 | "namedictionary_prefix_start_index"); 464 | if (kPrefixStartIndex == -1) { 465 | // TODO(indutny): check V8 version? 466 | kPrefixStartIndex = kEntrySize; 467 | } 468 | 469 | kPrefixSize = LoadConstant("class_NameDictionaryShape__prefix_size__int", 470 | "namedictionaryshape_prefix_size") + 471 | kPrefixStartIndex; 472 | } 473 | 474 | 475 | void Frame::Load() { 476 | kContextOffset = LoadConstant("off_fp_context"); 477 | kFunctionOffset = LoadConstant("off_fp_function"); 478 | kArgsOffset = LoadConstant("off_fp_args"); 479 | 480 | // NOTE: Starting from 5.1.71 these two reside in the same field 481 | kMarkerOffset = LoadConstant("off_fp_marker", "off_fp_context"); 482 | 483 | kAdaptorFrame = LoadConstant("frametype_ArgumentsAdaptorFrame"); 484 | kEntryFrame = LoadConstant("frametype_EntryFrame"); 485 | kEntryConstructFrame = LoadConstant("frametype_EntryConstructFrame"); 486 | kExitFrame = LoadConstant("frametype_ExitFrame"); 487 | kInternalFrame = LoadConstant("frametype_InternalFrame"); 488 | kConstructFrame = LoadConstant("frametype_ConstructFrame"); 489 | kJSFrame = LoadConstant("frametype_JavaScriptFrame"); 490 | kOptimizedFrame = LoadConstant("frametype_OptimizedFrame"); 491 | } 492 | 493 | 494 | void Types::Load() { 495 | kFirstNonstringType = LoadConstant("FirstNonstringType"); 496 | 497 | kHeapNumberType = LoadConstant("type_HeapNumber__HEAP_NUMBER_TYPE"); 498 | kMapType = LoadConstant("type_Map__MAP_TYPE"); 499 | kGlobalObjectType = 500 | LoadConstant("type_JSGlobalObject__JS_GLOBAL_OBJECT_TYPE"); 501 | kOddballType = LoadConstant("type_Oddball__ODDBALL_TYPE"); 502 | kJSObjectType = LoadConstant("type_JSObject__JS_OBJECT_TYPE"); 503 | kJSAPIObjectType = LoadConstant("APIObjectType"); 504 | kJSSpecialAPIObjectType = LoadConstant("APISpecialObjectType"); 505 | kJSArrayType = LoadConstant("type_JSArray__JS_ARRAY_TYPE"); 506 | kCodeType = LoadConstant("type_Code__CODE_TYPE"); 507 | kJSFunctionType = LoadConstant("type_JSFunction__JS_FUNCTION_TYPE"); 508 | kFixedArrayType = LoadConstant("type_FixedArray__FIXED_ARRAY_TYPE"); 509 | kJSArrayBufferType = LoadConstant("type_JSArrayBuffer__JS_ARRAY_BUFFER_TYPE"); 510 | kJSTypedArrayType = LoadConstant("type_JSTypedArray__JS_TYPED_ARRAY_TYPE"); 511 | kJSRegExpType = LoadConstant("type_JSRegExp__JS_REGEXP_TYPE"); 512 | kJSDateType = LoadConstant("type_JSDate__JS_DATE_TYPE"); 513 | kSharedFunctionInfoType = 514 | LoadConstant("type_SharedFunctionInfo__SHARED_FUNCTION_INFO_TYPE"); 515 | kScriptType = LoadConstant("type_Script__SCRIPT_TYPE"); 516 | 517 | if (kJSAPIObjectType == -1) { 518 | common_->Load(); 519 | 520 | if (common_->CheckLowestVersion(5, 2, 12)) 521 | kJSAPIObjectType = kJSObjectType - 1; 522 | } 523 | } 524 | 525 | } // namespace constants 526 | } // namespace v8 527 | } // namespace llnode 528 | -------------------------------------------------------------------------------- /src/llv8-constants.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LLV8_CONSTANTS_H_ 2 | #define SRC_LLV8_CONSTANTS_H_ 3 | 4 | #include 5 | 6 | namespace llnode { 7 | namespace v8 { 8 | namespace constants { 9 | 10 | // Forward declarations 11 | class Common; 12 | 13 | class Module { 14 | public: 15 | Module() : loaded_(false) {} 16 | 17 | inline bool is_loaded() const { return loaded_; } 18 | 19 | void Assign(lldb::SBTarget target, Common* common = nullptr); 20 | 21 | protected: 22 | int64_t LoadRawConstant(const char* name, int64_t def = -1); 23 | int64_t LoadConstant(const char* name, int64_t def = -1); 24 | int64_t LoadConstant(const char* name, const char* fallback, 25 | int64_t def = -1); 26 | 27 | lldb::SBTarget target_; 28 | Common* common_; 29 | bool loaded_; 30 | }; 31 | 32 | #define MODULE_DEFAULT_METHODS(NAME) \ 33 | NAME() {} \ 34 | inline NAME* operator()() { \ 35 | if (loaded_) return this; \ 36 | loaded_ = true; \ 37 | Load(); \ 38 | return this; \ 39 | } 40 | 41 | class Common : public Module { 42 | public: 43 | MODULE_DEFAULT_METHODS(Common); 44 | 45 | int64_t kPointerSize; 46 | int64_t kVersionMajor; 47 | int64_t kVersionMinor; 48 | int64_t kVersionPatch; 49 | 50 | bool CheckLowestVersion(int64_t major, int64_t minor, int64_t patch); 51 | bool CheckHighestVersion(int64_t major, int64_t minor, int64_t patch); 52 | 53 | // Public, because other modules may use it 54 | void Load(); 55 | }; 56 | 57 | class Smi : public Module { 58 | public: 59 | MODULE_DEFAULT_METHODS(Smi); 60 | 61 | int64_t kTag; 62 | int64_t kTagMask; 63 | int64_t kShiftSize; 64 | 65 | protected: 66 | void Load(); 67 | }; 68 | 69 | class HeapObject : public Module { 70 | public: 71 | MODULE_DEFAULT_METHODS(HeapObject); 72 | 73 | int64_t kTag; 74 | int64_t kTagMask; 75 | 76 | int64_t kMapOffset; 77 | 78 | protected: 79 | void Load(); 80 | }; 81 | 82 | class Map : public Module { 83 | public: 84 | MODULE_DEFAULT_METHODS(Map); 85 | 86 | int64_t kInstanceAttrsOffset; 87 | int64_t kMaybeConstructorOffset; 88 | int64_t kInstanceDescriptorsOffset; 89 | int64_t kBitField3Offset; 90 | int64_t kInObjectPropertiesOffset; 91 | int64_t kInstanceSizeOffset; 92 | 93 | int64_t kNumberOfOwnDescriptorsMask; 94 | int64_t kNumberOfOwnDescriptorsShift; 95 | int64_t kDictionaryMapShift; 96 | 97 | protected: 98 | void Load(); 99 | }; 100 | 101 | class JSObject : public Module { 102 | public: 103 | MODULE_DEFAULT_METHODS(JSObject); 104 | 105 | int64_t kPropertiesOffset; 106 | int64_t kElementsOffset; 107 | int64_t kInternalFieldsOffset; 108 | 109 | protected: 110 | void Load(); 111 | }; 112 | 113 | class HeapNumber : public Module { 114 | public: 115 | MODULE_DEFAULT_METHODS(HeapNumber); 116 | 117 | int64_t kValueOffset; 118 | 119 | protected: 120 | void Load(); 121 | }; 122 | 123 | class JSArray : public Module { 124 | public: 125 | MODULE_DEFAULT_METHODS(JSArray); 126 | 127 | int64_t kLengthOffset; 128 | 129 | protected: 130 | void Load(); 131 | }; 132 | 133 | class JSFunction : public Module { 134 | public: 135 | MODULE_DEFAULT_METHODS(JSFunction); 136 | 137 | int64_t kSharedInfoOffset; 138 | int64_t kContextOffset; 139 | 140 | protected: 141 | void Load(); 142 | }; 143 | 144 | class JSRegExp : public Module { 145 | public: 146 | MODULE_DEFAULT_METHODS(JSRegExp); 147 | 148 | int64_t kSourceOffset; 149 | 150 | protected: 151 | void Load(); 152 | }; 153 | 154 | class JSDate : public Module { 155 | public: 156 | MODULE_DEFAULT_METHODS(JSDate); 157 | 158 | int64_t kValueOffset; 159 | 160 | protected: 161 | void Load(); 162 | }; 163 | 164 | class SharedInfo : public Module { 165 | public: 166 | MODULE_DEFAULT_METHODS(SharedInfo); 167 | 168 | int64_t kNameOffset; 169 | int64_t kInferredNameOffset; 170 | int64_t kScriptOffset; 171 | int64_t kCodeOffset; 172 | int64_t kStartPositionOffset; 173 | int64_t kEndPositionOffset; 174 | int64_t kParameterCountOffset; 175 | int64_t kScopeInfoOffset; 176 | 177 | int64_t kStartPositionMask; 178 | int64_t kStartPositionShift; 179 | 180 | protected: 181 | void Load(); 182 | }; 183 | 184 | class Code : public Module { 185 | public: 186 | MODULE_DEFAULT_METHODS(Code) 187 | 188 | int64_t kStartOffset; 189 | int64_t kSizeOffset; 190 | 191 | protected: 192 | void Load(); 193 | }; 194 | 195 | class ScopeInfo : public Module { 196 | public: 197 | MODULE_DEFAULT_METHODS(ScopeInfo); 198 | 199 | int64_t kParameterCountOffset; 200 | int64_t kStackLocalCountOffset; 201 | int64_t kContextLocalCountOffset; 202 | int64_t kContextGlobalCountOffset; 203 | int64_t kVariablePartIndex; 204 | 205 | protected: 206 | void Load(); 207 | }; 208 | 209 | class Context : public Module { 210 | public: 211 | MODULE_DEFAULT_METHODS(Context); 212 | 213 | int64_t kClosureIndex; 214 | int64_t kGlobalObjectIndex; 215 | int64_t kPreviousIndex; 216 | int64_t kMinContextSlots; 217 | 218 | protected: 219 | void Load(); 220 | }; 221 | 222 | class Script : public Module { 223 | public: 224 | MODULE_DEFAULT_METHODS(Script); 225 | 226 | int64_t kNameOffset; 227 | int64_t kLineOffsetOffset; 228 | int64_t kSourceOffset; 229 | int64_t kLineEndsOffset; 230 | 231 | protected: 232 | void Load(); 233 | }; 234 | 235 | class String : public Module { 236 | public: 237 | MODULE_DEFAULT_METHODS(String); 238 | 239 | int64_t kEncodingMask; 240 | int64_t kRepresentationMask; 241 | 242 | // Encoding 243 | int64_t kOneByteStringTag; 244 | int64_t kTwoByteStringTag; 245 | 246 | // Representation 247 | int64_t kSeqStringTag; 248 | int64_t kConsStringTag; 249 | int64_t kSlicedStringTag; 250 | int64_t kExternalStringTag; 251 | 252 | int64_t kLengthOffset; 253 | 254 | protected: 255 | void Load(); 256 | }; 257 | 258 | class OneByteString : public Module { 259 | public: 260 | MODULE_DEFAULT_METHODS(OneByteString); 261 | 262 | int64_t kCharsOffset; 263 | 264 | protected: 265 | void Load(); 266 | }; 267 | 268 | class TwoByteString : public Module { 269 | public: 270 | MODULE_DEFAULT_METHODS(TwoByteString); 271 | 272 | int64_t kCharsOffset; 273 | 274 | protected: 275 | void Load(); 276 | }; 277 | 278 | class ConsString : public Module { 279 | public: 280 | MODULE_DEFAULT_METHODS(ConsString); 281 | 282 | int64_t kFirstOffset; 283 | int64_t kSecondOffset; 284 | 285 | protected: 286 | void Load(); 287 | }; 288 | 289 | class SlicedString : public Module { 290 | public: 291 | MODULE_DEFAULT_METHODS(SlicedString); 292 | 293 | int64_t kParentOffset; 294 | int64_t kOffsetOffset; 295 | 296 | protected: 297 | void Load(); 298 | }; 299 | 300 | class FixedArrayBase : public Module { 301 | public: 302 | MODULE_DEFAULT_METHODS(FixedArrayBase); 303 | 304 | int64_t kLengthOffset; 305 | 306 | protected: 307 | void Load(); 308 | }; 309 | 310 | class FixedArray : public Module { 311 | public: 312 | MODULE_DEFAULT_METHODS(FixedArray); 313 | 314 | int64_t kDataOffset; 315 | 316 | protected: 317 | void Load(); 318 | }; 319 | 320 | class Oddball : public Module { 321 | public: 322 | MODULE_DEFAULT_METHODS(Oddball); 323 | 324 | int64_t kKindOffset; 325 | 326 | int64_t kException; 327 | int64_t kFalse; 328 | int64_t kTrue; 329 | int64_t kUndefined; 330 | int64_t kNull; 331 | int64_t kTheHole; 332 | int64_t kUninitialized; 333 | 334 | protected: 335 | void Load(); 336 | }; 337 | 338 | class JSArrayBuffer : public Module { 339 | public: 340 | MODULE_DEFAULT_METHODS(JSArrayBuffer); 341 | 342 | int64_t kKindOffset; 343 | 344 | int64_t kBackingStoreOffset; 345 | int64_t kByteLengthOffset; 346 | int64_t kBitFieldOffset; 347 | 348 | int64_t kWasNeuteredMask; 349 | int64_t kWasNeuteredShift; 350 | 351 | protected: 352 | void Load(); 353 | }; 354 | 355 | class JSArrayBufferView : public Module { 356 | public: 357 | MODULE_DEFAULT_METHODS(JSArrayBufferView); 358 | 359 | int64_t kBufferOffset; 360 | int64_t kByteOffsetOffset; 361 | int64_t kByteLengthOffset; 362 | 363 | protected: 364 | void Load(); 365 | }; 366 | 367 | class DescriptorArray : public Module { 368 | public: 369 | MODULE_DEFAULT_METHODS(DescriptorArray); 370 | 371 | int64_t kDetailsOffset; 372 | int64_t kKeyOffset; 373 | int64_t kValueOffset; 374 | 375 | int64_t kPropertyIndexMask; 376 | int64_t kPropertyIndexShift; 377 | int64_t kPropertyTypeMask; 378 | int64_t kRepresentationMask; 379 | int64_t kRepresentationShift; 380 | 381 | int64_t kFieldType; 382 | int64_t kConstFieldType; 383 | int64_t kRepresentationDouble; 384 | 385 | int64_t kFirstIndex; 386 | int64_t kSize; 387 | 388 | protected: 389 | void Load(); 390 | }; 391 | 392 | class NameDictionary : public Module { 393 | public: 394 | MODULE_DEFAULT_METHODS(NameDictionary); 395 | 396 | int64_t kKeyOffset; 397 | int64_t kValueOffset; 398 | 399 | int64_t kEntrySize; 400 | int64_t kPrefixStartIndex; 401 | int64_t kPrefixSize; 402 | 403 | protected: 404 | void Load(); 405 | }; 406 | 407 | class Frame : public Module { 408 | public: 409 | MODULE_DEFAULT_METHODS(Frame); 410 | 411 | int64_t kContextOffset; 412 | int64_t kFunctionOffset; 413 | int64_t kArgsOffset; 414 | int64_t kMarkerOffset; 415 | 416 | int64_t kAdaptorFrame; 417 | int64_t kEntryFrame; 418 | int64_t kEntryConstructFrame; 419 | int64_t kExitFrame; 420 | int64_t kInternalFrame; 421 | int64_t kConstructFrame; 422 | int64_t kJSFrame; 423 | int64_t kOptimizedFrame; 424 | 425 | protected: 426 | void Load(); 427 | }; 428 | 429 | class Types : public Module { 430 | public: 431 | MODULE_DEFAULT_METHODS(Types); 432 | 433 | int64_t kFirstNonstringType; 434 | 435 | int64_t kHeapNumberType; 436 | int64_t kMapType; 437 | int64_t kGlobalObjectType; 438 | int64_t kOddballType; 439 | int64_t kJSObjectType; 440 | int64_t kJSAPIObjectType; 441 | int64_t kJSSpecialAPIObjectType; 442 | int64_t kJSArrayType; 443 | int64_t kCodeType; 444 | int64_t kJSFunctionType; 445 | int64_t kFixedArrayType; 446 | int64_t kJSArrayBufferType; 447 | int64_t kJSTypedArrayType; 448 | int64_t kJSRegExpType; 449 | int64_t kJSDateType; 450 | int64_t kSharedFunctionInfoType; 451 | int64_t kScriptType; 452 | 453 | protected: 454 | void Load(); 455 | }; 456 | 457 | #undef MODULE_DEFAULT_METHODS 458 | 459 | } // namespace constants 460 | } // namespace v8 461 | } // namespace llnode 462 | 463 | #endif // SRC_LLV8_CONSTANTS_H_ 464 | -------------------------------------------------------------------------------- /src/llv8-inl.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LLV8_INL_H_ 2 | #define SRC_LLV8_INL_H_ 3 | 4 | #include "llv8.h" 5 | 6 | namespace llnode { 7 | namespace v8 { 8 | 9 | template <> 10 | inline double LLV8::LoadValue(int64_t addr, Error& err) { 11 | return LoadDouble(addr, err); 12 | } 13 | 14 | template 15 | inline T LLV8::LoadValue(int64_t addr, Error& err) { 16 | int64_t ptr; 17 | ptr = LoadPtr(addr, err); 18 | if (err.Fail()) return T(); 19 | 20 | T res = T(this, ptr); 21 | if (!res.Check()) { 22 | err = Error::Failure("Invalid value"); 23 | return T(); 24 | } 25 | 26 | return res; 27 | } 28 | 29 | 30 | inline bool Smi::Check() const { 31 | return (raw() & v8()->smi()->kTagMask) == v8()->smi()->kTag; 32 | } 33 | 34 | 35 | inline int64_t Smi::GetValue() const { 36 | return raw() >> (v8()->smi()->kShiftSize + v8()->smi()->kTagMask); 37 | } 38 | 39 | 40 | inline bool HeapObject::Check() const { 41 | return (raw() & v8()->heap_obj()->kTagMask) == v8()->heap_obj()->kTag; 42 | } 43 | 44 | 45 | int64_t HeapObject::LeaField(int64_t off) const { 46 | return raw() - v8()->heap_obj()->kTag + off; 47 | } 48 | 49 | 50 | inline int64_t HeapObject::LoadField(int64_t off, Error& err) { 51 | return v8()->LoadPtr(LeaField(off), err); 52 | } 53 | 54 | 55 | template <> 56 | inline double HeapObject::LoadFieldValue(int64_t off, Error& err) { 57 | return v8()->LoadValue(LeaField(off), err); 58 | } 59 | 60 | 61 | template 62 | inline T HeapObject::LoadFieldValue(int64_t off, Error& err) { 63 | T res = v8()->LoadValue(LeaField(off), err); 64 | if (err.Fail()) return T(); 65 | if (!res.Check()) { 66 | err = Error::Failure("Invalid value"); 67 | return T(); 68 | } 69 | 70 | return res; 71 | } 72 | 73 | 74 | inline int64_t HeapObject::GetType(Error& err) { 75 | HeapObject obj = GetMap(err); 76 | if (err.Fail()) return -1; 77 | 78 | Map map(obj); 79 | return map.GetType(err); 80 | } 81 | 82 | 83 | inline int64_t Map::GetType(Error& err) { 84 | int64_t type = LoadField(v8()->map()->kInstanceAttrsOffset, err); 85 | if (err.Fail()) return -1; 86 | return type & 0xff; 87 | } 88 | 89 | 90 | inline JSFunction JSFrame::GetFunction(Error& err) { 91 | return v8()->LoadValue(raw() + v8()->frame()->kFunctionOffset, 92 | err); 93 | } 94 | 95 | 96 | inline int64_t JSFrame::LeaParamSlot(int slot, int count) const { 97 | return raw() + v8()->frame()->kArgsOffset + 98 | (count - slot - 1) * v8()->common()->kPointerSize; 99 | } 100 | 101 | 102 | inline Value JSFrame::GetReceiver(int count, Error& err) { 103 | return GetParam(-1, count, err); 104 | } 105 | 106 | 107 | inline Value JSFrame::GetParam(int slot, int count, Error& err) { 108 | int64_t addr = LeaParamSlot(slot, count); 109 | return v8()->LoadValue(addr, err); 110 | } 111 | 112 | 113 | inline std::string JSFunction::Name(Error& err) { 114 | SharedFunctionInfo info = Info(err); 115 | if (err.Fail()) return std::string(); 116 | 117 | return info.ProperName(err); 118 | } 119 | 120 | 121 | inline bool Map::IsDictionary(Error& err) { 122 | int64_t field = BitField3(err); 123 | if (err.Fail()) return false; 124 | 125 | return (field & (1 << v8()->map()->kDictionaryMapShift)) != 0; 126 | } 127 | 128 | 129 | inline int64_t Map::NumberOfOwnDescriptors(Error& err) { 130 | int64_t field = BitField3(err); 131 | if (err.Fail()) return false; 132 | 133 | // Skip EnumLength 134 | field &= v8()->map()->kNumberOfOwnDescriptorsMask; 135 | field >>= v8()->map()->kNumberOfOwnDescriptorsShift; 136 | return field; 137 | } 138 | 139 | 140 | #define ACCESSOR(CLASS, METHOD, OFF, TYPE) \ 141 | inline TYPE CLASS::METHOD(Error& err) { \ 142 | return LoadFieldValue(v8()->OFF, err); \ 143 | } 144 | 145 | 146 | ACCESSOR(HeapObject, GetMap, heap_obj()->kMapOffset, HeapObject) 147 | 148 | ACCESSOR(Map, MaybeConstructor, map()->kMaybeConstructorOffset, HeapObject) 149 | ACCESSOR(Map, InstanceDescriptors, map()->kInstanceDescriptorsOffset, 150 | HeapObject) 151 | 152 | inline int64_t Map::BitField3(Error& err) { 153 | return LoadField(v8()->map()->kBitField3Offset, err) & 0xffffffff; 154 | } 155 | 156 | inline int64_t Map::InObjectProperties(Error& err) { 157 | return LoadField(v8()->map()->kInObjectPropertiesOffset, err) & 0xff; 158 | } 159 | 160 | inline int64_t Map::InstanceSize(Error& err) { 161 | return (LoadField(v8()->map()->kInstanceSizeOffset, err) & 0xff) * 162 | v8()->common()->kPointerSize; 163 | } 164 | 165 | ACCESSOR(JSObject, Properties, js_object()->kPropertiesOffset, HeapObject) 166 | ACCESSOR(JSObject, Elements, js_object()->kElementsOffset, HeapObject) 167 | 168 | inline bool JSObject::IsObjectType(LLV8* v8, int64_t type) { 169 | return type == v8->types()->kJSObjectType || 170 | type == v8->types()->kJSAPIObjectType || 171 | type == v8->types()->kJSSpecialAPIObjectType; 172 | } 173 | 174 | ACCESSOR(HeapNumber, GetValue, heap_number()->kValueOffset, double) 175 | 176 | ACCESSOR(JSArray, Length, js_array()->kLengthOffset, Smi) 177 | 178 | ACCESSOR(JSRegExp, GetSource, js_regexp()->kSourceOffset, String) 179 | 180 | ACCESSOR(JSDate, GetValue, js_date()->kValueOffset, Value) 181 | 182 | inline int64_t String::Representation(Error& err) { 183 | int64_t type = GetType(err); 184 | if (err.Fail()) return -1; 185 | return type & v8()->string()->kRepresentationMask; 186 | } 187 | 188 | 189 | inline int64_t String::Encoding(Error& err) { 190 | int64_t type = GetType(err); 191 | if (err.Fail()) return -1; 192 | return type & v8()->string()->kEncodingMask; 193 | } 194 | 195 | ACCESSOR(String, Length, string()->kLengthOffset, Smi) 196 | 197 | ACCESSOR(Script, Name, script()->kNameOffset, String) 198 | ACCESSOR(Script, LineOffset, script()->kLineOffsetOffset, Smi) 199 | ACCESSOR(Script, Source, script()->kSourceOffset, HeapObject) 200 | ACCESSOR(Script, LineEnds, script()->kLineEndsOffset, HeapObject) 201 | 202 | ACCESSOR(SharedFunctionInfo, Name, shared_info()->kNameOffset, String) 203 | ACCESSOR(SharedFunctionInfo, InferredName, shared_info()->kInferredNameOffset, 204 | String) 205 | ACCESSOR(SharedFunctionInfo, GetScript, shared_info()->kScriptOffset, Script) 206 | ACCESSOR(SharedFunctionInfo, GetCode, shared_info()->kCodeOffset, Code) 207 | ACCESSOR(SharedFunctionInfo, GetScopeInfo, shared_info()->kScopeInfoOffset, 208 | HeapObject) 209 | 210 | inline int64_t Code::Start() { return LeaField(v8()->code()->kStartOffset); } 211 | 212 | inline int64_t Code::Size(Error& err) { 213 | return LoadField(v8()->code()->kSizeOffset, err) & 0xffffffff; 214 | } 215 | 216 | ACCESSOR(Oddball, Kind, oddball()->kKindOffset, Smi) 217 | 218 | inline int64_t JSArrayBuffer::BackingStore(Error& err) { 219 | return LoadField(v8()->js_array_buffer()->kBackingStoreOffset, err); 220 | } 221 | 222 | inline int64_t JSArrayBuffer::BitField(Error& err) { 223 | return LoadField(v8()->js_array_buffer()->kBitFieldOffset, err) & 0xffffffff; 224 | } 225 | 226 | ACCESSOR(JSArrayBuffer, ByteLength, js_array_buffer()->kByteLengthOffset, Smi) 227 | 228 | ACCESSOR(JSArrayBufferView, Buffer, js_array_buffer_view()->kBufferOffset, 229 | JSArrayBuffer) 230 | ACCESSOR(JSArrayBufferView, ByteOffset, 231 | js_array_buffer_view()->kByteOffsetOffset, Smi) 232 | ACCESSOR(JSArrayBufferView, ByteLength, 233 | js_array_buffer_view()->kByteLengthOffset, Smi) 234 | 235 | // TODO(indutny): this field is a Smi on 32bit 236 | inline int64_t SharedFunctionInfo::ParameterCount(Error& err) { 237 | int64_t field = LoadField(v8()->shared_info()->kParameterCountOffset, err); 238 | if (err.Fail()) return -1; 239 | 240 | field &= 0xffffffff; 241 | return field; 242 | } 243 | 244 | // TODO(indutny): this field is a Smi on 32bit 245 | inline int64_t SharedFunctionInfo::StartPosition(Error& err) { 246 | int64_t field = LoadField(v8()->shared_info()->kStartPositionOffset, err); 247 | if (err.Fail()) return -1; 248 | 249 | field &= 0xffffffff; 250 | 251 | field &= v8()->shared_info()->kStartPositionMask; 252 | field >>= v8()->shared_info()->kStartPositionShift; 253 | return field; 254 | } 255 | 256 | // TODO (hhellyer): as above, this field is different on 32bit. 257 | inline int64_t SharedFunctionInfo::EndPosition(Error& err) { 258 | int64_t field = LoadField(v8()->shared_info()->kEndPositionOffset, err); 259 | if (err.Fail()) return -1; 260 | 261 | field &= 0xffffffff; 262 | return field >> 1; 263 | } 264 | 265 | ACCESSOR(JSFunction, Info, js_function()->kSharedInfoOffset, 266 | SharedFunctionInfo); 267 | ACCESSOR(JSFunction, GetContext, js_function()->kContextOffset, HeapObject); 268 | 269 | ACCESSOR(ConsString, First, cons_string()->kFirstOffset, String); 270 | ACCESSOR(ConsString, Second, cons_string()->kSecondOffset, String); 271 | 272 | ACCESSOR(SlicedString, Parent, sliced_string()->kParentOffset, String); 273 | ACCESSOR(SlicedString, Offset, sliced_string()->kOffsetOffset, Smi); 274 | 275 | ACCESSOR(FixedArrayBase, Length, fixed_array_base()->kLengthOffset, Smi); 276 | 277 | inline std::string OneByteString::ToString(Error& err) { 278 | int64_t chars = LeaField(v8()->one_byte_string()->kCharsOffset); 279 | Smi len = Length(err); 280 | if (err.Fail()) return std::string(); 281 | return v8()->LoadString(chars, len.GetValue(), err); 282 | } 283 | 284 | inline std::string TwoByteString::ToString(Error& err) { 285 | int64_t chars = LeaField(v8()->two_byte_string()->kCharsOffset); 286 | Smi len = Length(err); 287 | if (err.Fail()) return std::string(); 288 | return v8()->LoadTwoByteString(chars, len.GetValue(), err); 289 | } 290 | 291 | inline std::string ConsString::ToString(Error& err) { 292 | String first = First(err); 293 | if (err.Fail()) return std::string(); 294 | 295 | String second = Second(err); 296 | if (err.Fail()) return std::string(); 297 | 298 | std::string tmp = first.ToString(err); 299 | if (err.Fail()) return std::string(); 300 | tmp += second.ToString(err); 301 | if (err.Fail()) return std::string(); 302 | 303 | return tmp; 304 | } 305 | 306 | inline std::string SlicedString::ToString(Error& err) { 307 | String parent = Parent(err); 308 | if (err.Fail()) return std::string(); 309 | 310 | Smi offset = Offset(err); 311 | if (err.Fail()) return std::string(); 312 | 313 | Smi length = Length(err); 314 | if (err.Fail()) return std::string(); 315 | 316 | std::string tmp = parent.ToString(err); 317 | if (err.Fail()) return std::string(); 318 | 319 | return tmp.substr(offset.GetValue(), length.GetValue()); 320 | } 321 | 322 | inline int64_t FixedArray::LeaData() const { 323 | return LeaField(v8()->fixed_array()->kDataOffset); 324 | } 325 | 326 | template 327 | inline T FixedArray::Get(int index, Error& err) { 328 | int64_t off = 329 | v8()->fixed_array()->kDataOffset + index * v8()->common()->kPointerSize; 330 | return LoadFieldValue(off, err); 331 | } 332 | 333 | inline Smi DescriptorArray::GetDetails(int index, Error& err) { 334 | return Get(v8()->descriptor_array()->kFirstIndex + 335 | index * v8()->descriptor_array()->kSize + 336 | v8()->descriptor_array()->kDetailsOffset, 337 | err); 338 | } 339 | 340 | inline Value DescriptorArray::GetKey(int index, Error& err) { 341 | return Get(v8()->descriptor_array()->kFirstIndex + 342 | index * v8()->descriptor_array()->kSize + 343 | v8()->descriptor_array()->kKeyOffset, 344 | err); 345 | } 346 | 347 | inline Value DescriptorArray::GetValue(int index, Error& err) { 348 | return Get(v8()->descriptor_array()->kFirstIndex + 349 | index * v8()->descriptor_array()->kSize + 350 | v8()->descriptor_array()->kValueOffset, 351 | err); 352 | } 353 | 354 | inline bool DescriptorArray::IsFieldDetails(Smi details) { 355 | return (details.GetValue() & v8()->descriptor_array()->kPropertyTypeMask) == 356 | v8()->descriptor_array()->kFieldType; 357 | } 358 | 359 | inline bool DescriptorArray::IsConstFieldDetails(Smi details) { 360 | return (details.GetValue() & v8()->descriptor_array()->kPropertyTypeMask) == 361 | v8()->descriptor_array()->kConstFieldType; 362 | } 363 | 364 | inline bool DescriptorArray::IsDoubleField(Smi details) { 365 | int64_t repr = details.GetValue(); 366 | repr &= v8()->descriptor_array()->kRepresentationMask; 367 | repr >>= v8()->descriptor_array()->kRepresentationShift; 368 | 369 | return repr == v8()->descriptor_array()->kRepresentationDouble; 370 | } 371 | 372 | inline int64_t DescriptorArray::FieldIndex(Smi details) { 373 | return (details.GetValue() & v8()->descriptor_array()->kPropertyIndexMask) >> 374 | v8()->descriptor_array()->kPropertyIndexShift; 375 | } 376 | 377 | inline Value NameDictionary::GetKey(int index, Error& err) { 378 | int64_t off = v8()->name_dictionary()->kPrefixSize + 379 | index * v8()->name_dictionary()->kEntrySize + 380 | v8()->name_dictionary()->kKeyOffset; 381 | return FixedArray::Get(off, err); 382 | } 383 | 384 | inline Value NameDictionary::GetValue(int index, Error& err) { 385 | int64_t off = v8()->name_dictionary()->kPrefixSize + 386 | index * v8()->name_dictionary()->kEntrySize + 387 | v8()->name_dictionary()->kValueOffset; 388 | return FixedArray::Get(off, err); 389 | } 390 | 391 | inline int64_t NameDictionary::Length(Error& err) { 392 | Smi length = FixedArray::Length(err); 393 | if (err.Fail()) return -1; 394 | 395 | int64_t res = length.GetValue() - v8()->name_dictionary()->kPrefixSize; 396 | res /= v8()->name_dictionary()->kEntrySize; 397 | return res; 398 | } 399 | 400 | inline JSFunction Context::Closure(Error& err) { 401 | return FixedArray::Get(v8()->context()->kClosureIndex, err); 402 | } 403 | 404 | inline Value Context::Previous(Error& err) { 405 | return FixedArray::Get(v8()->context()->kPreviousIndex, err); 406 | } 407 | 408 | inline Value Context::ContextSlot(int index, Error& err) { 409 | return FixedArray::Get(v8()->context()->kMinContextSlots + index, err); 410 | } 411 | 412 | inline Smi ScopeInfo::ParameterCount(Error& err) { 413 | return FixedArray::Get(v8()->scope_info()->kParameterCountOffset, err); 414 | } 415 | 416 | inline Smi ScopeInfo::StackLocalCount(Error& err) { 417 | return FixedArray::Get(v8()->scope_info()->kStackLocalCountOffset, err); 418 | } 419 | 420 | inline Smi ScopeInfo::ContextLocalCount(Error& err) { 421 | return FixedArray::Get(v8()->scope_info()->kContextLocalCountOffset, 422 | err); 423 | } 424 | 425 | inline Smi ScopeInfo::ContextGlobalCount(Error& err) { 426 | return FixedArray::Get(v8()->scope_info()->kContextGlobalCountOffset, 427 | err); 428 | } 429 | 430 | inline String ScopeInfo::ContextLocalName(int index, int param_count, 431 | int stack_count, Error& err) { 432 | int proper_index = index + stack_count + 1 + param_count; 433 | proper_index += v8()->scope_info()->kVariablePartIndex; 434 | return FixedArray::Get(proper_index, err); 435 | } 436 | 437 | inline bool Oddball::IsHoleOrUndefined(Error& err) { 438 | Smi kind = Kind(err); 439 | if (err.Fail()) return false; 440 | 441 | return kind.GetValue() == v8()->oddball()->kTheHole || 442 | kind.GetValue() == v8()->oddball()->kUndefined; 443 | } 444 | 445 | inline bool Oddball::IsHole(Error& err) { 446 | Smi kind = Kind(err); 447 | if (err.Fail()) return false; 448 | 449 | return kind.GetValue() == v8()->oddball()->kTheHole; 450 | } 451 | 452 | inline bool JSArrayBuffer::WasNeutered(Error& err) { 453 | int64_t field = BitField(err); 454 | if (err.Fail()) return false; 455 | 456 | field &= v8()->js_array_buffer()->kWasNeuteredMask; 457 | field >>= v8()->js_array_buffer()->kWasNeuteredShift; 458 | return field != 0; 459 | } 460 | 461 | #undef ACCESSOR 462 | 463 | } // namespace v8 464 | } // namespace llnode 465 | 466 | #endif // SRC_LLV8_INL_H_ 467 | -------------------------------------------------------------------------------- /src/llv8.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "llv8-inl.h" 6 | #include "llv8.h" 7 | 8 | namespace llnode { 9 | namespace v8 { 10 | 11 | using namespace lldb; 12 | 13 | static std::string kConstantPrefix = "v8dbg_"; 14 | 15 | void LLV8::Load(SBTarget target) { 16 | // Reload process anyway 17 | process_ = target.GetProcess(); 18 | 19 | // No need to reload 20 | if (target_ == target) return; 21 | 22 | target_ = target; 23 | 24 | common.Assign(target); 25 | smi.Assign(target, &common); 26 | heap_obj.Assign(target, &common); 27 | map.Assign(target, &common); 28 | js_object.Assign(target, &common); 29 | heap_number.Assign(target, &common); 30 | js_array.Assign(target, &common); 31 | js_function.Assign(target, &common); 32 | shared_info.Assign(target, &common); 33 | code.Assign(target, &common); 34 | scope_info.Assign(target, &common); 35 | context.Assign(target, &common); 36 | script.Assign(target, &common); 37 | string.Assign(target, &common); 38 | one_byte_string.Assign(target, &common); 39 | two_byte_string.Assign(target, &common); 40 | cons_string.Assign(target, &common); 41 | sliced_string.Assign(target, &common); 42 | fixed_array_base.Assign(target, &common); 43 | fixed_array.Assign(target, &common); 44 | oddball.Assign(target, &common); 45 | js_array_buffer.Assign(target, &common); 46 | js_array_buffer_view.Assign(target, &common); 47 | js_regexp.Assign(target, &common); 48 | js_date.Assign(target, &common); 49 | descriptor_array.Assign(target, &common); 50 | name_dictionary.Assign(target, &common); 51 | frame.Assign(target, &common); 52 | types.Assign(target, &common); 53 | } 54 | 55 | 56 | int64_t LLV8::LoadPtr(int64_t addr, Error& err) { 57 | SBError sberr; 58 | int64_t value = 59 | process_.ReadPointerFromMemory(static_cast(addr), sberr); 60 | if (sberr.Fail()) { 61 | // TODO(indutny): add more information 62 | err = Error::Failure("Failed to load V8 value"); 63 | return -1; 64 | } 65 | 66 | err = Error::Ok(); 67 | return value; 68 | } 69 | 70 | 71 | double LLV8::LoadDouble(int64_t addr, Error& err) { 72 | SBError sberr; 73 | int64_t value = process_.ReadUnsignedFromMemory(static_cast(addr), 74 | sizeof(double), sberr); 75 | if (sberr.Fail()) { 76 | // TODO(indutny): add more information 77 | err = Error::Failure("Failed to load V8 double value"); 78 | return -1.0; 79 | } 80 | 81 | err = Error::Ok(); 82 | return *reinterpret_cast(&value); 83 | } 84 | 85 | 86 | std::string LLV8::LoadString(int64_t addr, int64_t length, Error& err) { 87 | if (length < 0) { 88 | err = Error::Failure("Failed to load V8 one byte string - Invalid length"); 89 | return std::string(); 90 | } 91 | 92 | char* buf = new char[length + 1]; 93 | SBError sberr; 94 | process_.ReadMemory(static_cast(addr), buf, 95 | static_cast(length), sberr); 96 | if (sberr.Fail()) { 97 | // TODO(indutny): add more information 98 | err = Error::Failure("Failed to load V8 one byte string"); 99 | delete[] buf; 100 | return std::string(); 101 | } 102 | 103 | buf[length] = '\0'; 104 | 105 | std::string res = buf; 106 | delete[] buf; 107 | err = Error::Ok(); 108 | return res; 109 | } 110 | 111 | 112 | std::string LLV8::LoadTwoByteString(int64_t addr, int64_t length, Error& err) { 113 | if (length < 0) { 114 | err = Error::Failure("Failed to load V8 two byte string - Invalid length"); 115 | return std::string(); 116 | } 117 | 118 | char* buf = new char[length * 2 + 1]; 119 | SBError sberr; 120 | process_.ReadMemory(static_cast(addr), buf, 121 | static_cast(length * 2), sberr); 122 | if (sberr.Fail()) { 123 | // TODO(indutny): add more information 124 | err = Error::Failure("Failed to load V8 two byte string"); 125 | delete[] buf; 126 | return std::string(); 127 | } 128 | 129 | for (int64_t i = 0; i < length; i++) buf[i] = buf[i * 2]; 130 | buf[length] = '\0'; 131 | 132 | std::string res = buf; 133 | delete[] buf; 134 | err = Error::Ok(); 135 | return res; 136 | } 137 | 138 | 139 | uint8_t* LLV8::LoadChunk(int64_t addr, int64_t length, Error& err) { 140 | uint8_t* buf = new uint8_t[length]; 141 | SBError sberr; 142 | process_.ReadMemory(static_cast(addr), buf, 143 | static_cast(length), sberr); 144 | if (sberr.Fail()) { 145 | // TODO(indutny): add more information 146 | err = Error::Failure("Failed to load V8 memory chunk"); 147 | delete[] buf; 148 | return nullptr; 149 | } 150 | 151 | err = Error::Ok(); 152 | return buf; 153 | } 154 | 155 | 156 | // reset_line - make line_start absolute vs start of function 157 | // otherwise relative to last end 158 | // returns line cursor 159 | uint32_t JSFrame::GetSourceForDisplay(bool reset_line, uint32_t line_start, 160 | uint32_t line_limit, std::string lines[], 161 | uint32_t& lines_found, Error& err) { 162 | v8::JSFunction fn = GetFunction(err); 163 | if (err.Fail()) { 164 | return line_start; 165 | } 166 | 167 | v8::SharedFunctionInfo info = fn.Info(err); 168 | if (err.Fail()) { 169 | return line_start; 170 | } 171 | 172 | v8::Script script = info.GetScript(err); 173 | if (err.Fail()) { 174 | return line_start; 175 | } 176 | 177 | // Check if this is being run multiple times against the same frame 178 | // if not, reset last line 179 | if (reset_line) { 180 | uint32_t pos = info.StartPosition(err); 181 | if (err.Fail()) { 182 | return line_start; 183 | } 184 | int64_t tmp_line; 185 | int64_t tmp_col; 186 | script.GetLineColumnFromPos(pos, tmp_line, tmp_col, err); 187 | if (err.Fail()) { 188 | return line_start; 189 | } 190 | line_start += tmp_line; 191 | } 192 | 193 | script.GetLines(line_start, lines, line_limit, lines_found, err); 194 | if (err.Fail()) { 195 | const char* msg = err.GetMessage(); 196 | if (msg == nullptr) { 197 | err = Error(true, "Failed to get Function Source"); 198 | } 199 | return line_start; 200 | } 201 | return line_start + lines_found; 202 | } 203 | 204 | std::string JSFrame::Inspect(bool with_args, Error& err) { 205 | Value context = 206 | v8()->LoadValue(raw() + v8()->frame()->kContextOffset, err); 207 | if (err.Fail()) return std::string(); 208 | 209 | Smi smi_context = Smi(context); 210 | if (smi_context.Check() && 211 | smi_context.GetValue() == v8()->frame()->kAdaptorFrame) { 212 | return ""; 213 | } 214 | 215 | Value marker = 216 | v8()->LoadValue(raw() + v8()->frame()->kMarkerOffset, err); 217 | if (err.Fail()) return std::string(); 218 | 219 | Smi smi_marker(marker); 220 | if (smi_marker.Check()) { 221 | int64_t value = smi_marker.GetValue(); 222 | if (value == v8()->frame()->kEntryFrame) { 223 | return ""; 224 | } else if (value == v8()->frame()->kEntryConstructFrame) { 225 | return ""; 226 | } else if (value == v8()->frame()->kExitFrame) { 227 | return ""; 228 | } else if (value == v8()->frame()->kInternalFrame) { 229 | return ""; 230 | } else if (value == v8()->frame()->kConstructFrame) { 231 | return ""; 232 | } else if (value != v8()->frame()->kJSFrame && 233 | value != v8()->frame()->kOptimizedFrame) { 234 | err = Error::Failure("Unknown frame marker"); 235 | return std::string(); 236 | } 237 | } 238 | 239 | // We are dealing with function or internal code (probably stub) 240 | JSFunction fn = GetFunction(err); 241 | if (err.Fail()) return std::string(); 242 | 243 | int64_t fn_type = fn.GetType(err); 244 | if (err.Fail()) return std::string(); 245 | 246 | if (fn_type == v8()->types()->kCodeType) return ""; 247 | if (fn_type != v8()->types()->kJSFunctionType) return ""; 248 | 249 | std::string args; 250 | if (with_args) { 251 | args = InspectArgs(fn, err); 252 | if (err.Fail()) return std::string(); 253 | } 254 | 255 | char tmp[128]; 256 | snprintf(tmp, sizeof(tmp), " fn=0x%016" PRIx64, fn.raw()); 257 | return fn.GetDebugLine(args, err) + tmp; 258 | } 259 | 260 | 261 | std::string JSFrame::InspectArgs(JSFunction fn, Error& err) { 262 | SharedFunctionInfo info = fn.Info(err); 263 | if (err.Fail()) return std::string(); 264 | 265 | int64_t param_count = info.ParameterCount(err); 266 | if (err.Fail()) return std::string(); 267 | 268 | Value receiver = GetReceiver(param_count, err); 269 | if (err.Fail()) return std::string(); 270 | 271 | InspectOptions options; 272 | 273 | std::string res = "this=" + receiver.Inspect(&options, err); 274 | if (err.Fail()) return std::string(); 275 | 276 | for (int64_t i = 0; i < param_count; i++) { 277 | Value param = GetParam(i, param_count, err); 278 | if (err.Fail()) return std::string(); 279 | 280 | res += ", " + param.Inspect(&options, err); 281 | if (err.Fail()) return std::string(); 282 | } 283 | 284 | return res; 285 | } 286 | 287 | 288 | std::string JSFunction::GetDebugLine(std::string args, Error& err) { 289 | SharedFunctionInfo info = Info(err); 290 | if (err.Fail()) return std::string(); 291 | 292 | std::string res = info.ProperName(err); 293 | if (err.Fail()) return std::string(); 294 | 295 | if (!args.empty()) res += "(" + args + ")"; 296 | 297 | res += " at "; 298 | 299 | std::string shared; 300 | 301 | res += info.GetPostfix(err); 302 | if (err.Fail()) return std::string(); 303 | 304 | return res; 305 | } 306 | 307 | 308 | std::string JSFunction::Inspect(InspectOptions* options, Error& err) { 309 | std::string res = "detailed) { 313 | HeapObject context_obj = GetContext(err); 314 | if (err.Fail()) return std::string(); 315 | 316 | Context context(context_obj); 317 | 318 | char tmp[128]; 319 | snprintf(tmp, sizeof(tmp), "\n context=0x%016" PRIx64, context.raw()); 320 | res += tmp; 321 | 322 | std::string context_str = context.Inspect(err); 323 | if (err.Fail()) return std::string(); 324 | 325 | if (!context_str.empty()) res += "{\n" + context_str + "}"; 326 | 327 | if (options->print_source) { 328 | SharedFunctionInfo info = Info(err); 329 | if (err.Fail()) return res; 330 | 331 | String name = info.Name(err); 332 | if (err.Fail()) return res; 333 | std::string name_str = name.ToString(err); 334 | 335 | if (err.Fail() || name_str.empty()) { 336 | String inferred_name = info.InferredName(err); 337 | if (err.Fail()) return res; 338 | 339 | name_str = inferred_name.ToString(err); 340 | if (err.Fail()) return res; 341 | } 342 | 343 | std::string source = GetSource(err); 344 | if (!err.Fail()) { 345 | res += "\n source:\n"; 346 | // name_str may be an empty string but that will match 347 | // the syntax for an anonymous function declaration correctly. 348 | res += "function " + name_str; 349 | res += source + "\n"; 350 | } 351 | } 352 | } 353 | 354 | return res + ">"; 355 | } 356 | 357 | 358 | std::string JSFunction::GetSource(Error& err) { 359 | v8::SharedFunctionInfo info = Info(err); 360 | if (err.Fail()) { 361 | return std::string(); 362 | } 363 | 364 | v8::Script script = info.GetScript(err); 365 | if (err.Fail()) { 366 | return std::string(); 367 | } 368 | 369 | // There is no `Script` for functions created in C++ (and possibly others) 370 | int64_t type = script.GetType(err); 371 | if (err.Fail()) { 372 | return std::string(); 373 | } 374 | 375 | if (type != v8()->types()->kScriptType) { 376 | return std::string(); 377 | } 378 | 379 | HeapObject source = script.Source(err); 380 | if (err.Fail()) return std::string(); 381 | 382 | int64_t source_type = source.GetType(err); 383 | if (err.Fail()) return std::string(); 384 | 385 | // No source 386 | if (source_type > v8()->types()->kFirstNonstringType) { 387 | err = Error(true, "No source"); 388 | return std::string(); 389 | } 390 | 391 | String str(source); 392 | std::string source_str = str.ToString(err); 393 | 394 | int64_t start_pos = info.StartPosition(err); 395 | 396 | if (err.Fail()) { 397 | return std::string(); 398 | } 399 | 400 | int64_t end_pos = info.EndPosition(err); 401 | 402 | if (err.Fail()) { 403 | return std::string(); 404 | } 405 | 406 | int64_t source_len = source_str.length(); 407 | 408 | if (end_pos > source_len) { 409 | end_pos = source_len; 410 | } 411 | int64_t len = end_pos - start_pos; 412 | 413 | std::string res = source_str.substr(start_pos, len); 414 | 415 | return res; 416 | } 417 | 418 | 419 | std::string JSRegExp::Inspect(InspectOptions* options, Error& err) { 420 | if (v8()->js_regexp()->kSourceOffset == -1) 421 | return JSObject::Inspect(options, err); 422 | 423 | std::string res = "detailed) { 432 | res += " " + InspectProperties(err); 433 | if (err.Fail()) return std::string(); 434 | } 435 | 436 | res += ">"; 437 | return res; 438 | } 439 | 440 | 441 | std::string JSDate::Inspect(Error& err) { 442 | std::string pre = ""; 451 | } 452 | 453 | return pre + s + ">"; 454 | } 455 | 456 | HeapNumber hn(val); 457 | if (hn.Check()) { 458 | std::string s = hn.ToString(true, err); 459 | if (err.Fail()) { 460 | return pre + ">"; 461 | } 462 | return pre + s + ">"; 463 | } 464 | 465 | double d = static_cast(val.raw()); 466 | char buf[128]; 467 | snprintf(buf, sizeof(buf), "%f", d); 468 | 469 | return pre + ">"; 470 | } 471 | 472 | 473 | std::string SharedFunctionInfo::ProperName(Error& err) { 474 | String name = Name(err); 475 | if (err.Fail()) return std::string(); 476 | 477 | std::string res = name.ToString(err); 478 | if (err.Fail() || res.empty()) { 479 | name = InferredName(err); 480 | if (err.Fail()) return std::string(); 481 | 482 | res = name.ToString(err); 483 | if (err.Fail()) return std::string(); 484 | } 485 | 486 | if (res.empty()) res = "(anonymous)"; 487 | 488 | return res; 489 | } 490 | 491 | 492 | std::string SharedFunctionInfo::GetPostfix(Error& err) { 493 | Script script = GetScript(err); 494 | if (err.Fail()) return std::string(); 495 | 496 | // There is no `Script` for functions created in C++ (and possibly others) 497 | int64_t type = script.GetType(err); 498 | if (err.Fail() || type != v8()->types()->kScriptType) 499 | return std::string("(no script)"); 500 | 501 | String name = script.Name(err); 502 | if (err.Fail()) return std::string(); 503 | 504 | int64_t start_pos = StartPosition(err); 505 | if (err.Fail()) return std::string(); 506 | 507 | std::string res = name.ToString(err); 508 | if (res.empty()) res = "(no script)"; 509 | 510 | int64_t line = 0; 511 | int64_t column = 0; 512 | script.GetLineColumnFromPos(start_pos, line, column, err); 513 | if (err.Fail()) return std::string(); 514 | 515 | // NOTE: lines start from 1 in most of editors 516 | char tmp[128]; 517 | snprintf(tmp, sizeof(tmp), ":%d:%d", static_cast(line + 1), 518 | static_cast(column)); 519 | return res + tmp; 520 | } 521 | 522 | std::string SharedFunctionInfo::ToString(Error& err) { 523 | std::string res = ProperName(err); 524 | if (err.Fail()) return std::string(); 525 | 526 | return res + " at " + GetPostfix(err); 527 | } 528 | 529 | 530 | // return end_char+1, which may be less than line_limit if source 531 | // ends before end_inclusive 532 | void Script::GetLines(uint64_t start_line, std::string lines[], 533 | uint64_t line_limit, uint32_t& lines_found, Error& err) { 534 | lines_found = 0; 535 | 536 | HeapObject source = Source(err); 537 | if (err.Fail()) return; 538 | 539 | int64_t type = source.GetType(err); 540 | if (err.Fail()) return; 541 | 542 | // No source 543 | if (type > v8()->types()->kFirstNonstringType) { 544 | err = Error(true, "No source"); 545 | return; 546 | } 547 | 548 | String str(source); 549 | std::string source_str = str.ToString(err); 550 | uint64_t limit = source_str.length(); 551 | 552 | uint64_t length = 0; 553 | uint64_t line_i = 0; 554 | uint64_t i = 0; 555 | for (; i < limit && lines_found < line_limit; i++) { 556 | // \r\n should ski adding a line and column on \r 557 | if (source_str[i] == '\r' && i < limit - 1 && source_str[i + 1] == '\n') { 558 | i++; 559 | } 560 | if (source_str[i] == '\n' || source_str[i] == '\r') { 561 | if (line_i >= start_line) { 562 | lines[lines_found] = std::string(source_str, i - length, length); 563 | lines_found++; 564 | } 565 | line_i++; 566 | length = 0; 567 | } else { 568 | length++; 569 | } 570 | } 571 | if (line_i >= start_line && length != 0 && lines_found < line_limit) { 572 | lines[lines_found] = std::string(source_str, limit - length, length); 573 | lines_found++; 574 | } 575 | } 576 | 577 | 578 | void Script::GetLineColumnFromPos(int64_t pos, int64_t& line, int64_t& column, 579 | Error& err) { 580 | line = 0; 581 | column = 0; 582 | 583 | HeapObject source = Source(err); 584 | if (err.Fail()) return; 585 | 586 | int64_t type = source.GetType(err); 587 | if (err.Fail()) return; 588 | 589 | // No source 590 | if (type > v8()->types()->kFirstNonstringType) { 591 | err = Error(true, "No source"); 592 | return; 593 | } 594 | 595 | String str(source); 596 | std::string source_str = str.ToString(err); 597 | int64_t limit = source_str.length(); 598 | if (limit > pos) limit = pos; 599 | 600 | for (int64_t i = 0; i < limit; i++, column++) { 601 | // \r\n should ski adding a line and column on \r 602 | if (source_str[i] == '\r' && i < limit - 1 && source_str[i + 1] == '\n') { 603 | i++; 604 | } 605 | if (source_str[i] == '\n' || source_str[i] == '\r') { 606 | column = 0; 607 | line++; 608 | } 609 | } 610 | } 611 | 612 | bool Value::IsHoleOrUndefined(Error& err) { 613 | HeapObject obj(this); 614 | if (!obj.Check()) return false; 615 | 616 | int64_t type = obj.GetType(err); 617 | if (err.Fail()) return false; 618 | 619 | if (type != v8()->types()->kOddballType) return false; 620 | 621 | Oddball odd(this); 622 | return odd.IsHoleOrUndefined(err); 623 | } 624 | 625 | 626 | // TODO(indutny): deduplicate this? 627 | bool Value::IsHole(Error& err) { 628 | HeapObject obj(this); 629 | if (!obj.Check()) return false; 630 | 631 | int64_t type = obj.GetType(err); 632 | if (err.Fail()) return false; 633 | 634 | if (type != v8()->types()->kOddballType) return false; 635 | 636 | Oddball odd(this); 637 | return odd.IsHole(err); 638 | } 639 | 640 | 641 | std::string Value::Inspect(InspectOptions* options, Error& err) { 642 | Smi smi(this); 643 | if (smi.Check()) return smi.Inspect(err); 644 | 645 | HeapObject obj(this); 646 | if (!obj.Check()) { 647 | err = Error::Failure("Not object and not smi"); 648 | return std::string(); 649 | } 650 | 651 | return obj.Inspect(options, err); 652 | } 653 | 654 | 655 | std::string Value::GetTypeName(Error& err) { 656 | Smi smi(this); 657 | if (smi.Check()) return "(Smi)"; 658 | 659 | HeapObject obj(this); 660 | if (!obj.Check()) { 661 | err = Error::Failure("Not object and not smi"); 662 | return std::string(); 663 | } 664 | 665 | return obj.GetTypeName(err); 666 | } 667 | 668 | 669 | std::string Value::ToString(Error& err) { 670 | Smi smi(this); 671 | if (smi.Check()) return smi.ToString(err); 672 | 673 | HeapObject obj(this); 674 | if (!obj.Check()) { 675 | err = Error::Failure("Not object and not smi"); 676 | return std::string(); 677 | } 678 | 679 | return obj.ToString(err); 680 | } 681 | 682 | 683 | std::string HeapObject::ToString(Error& err) { 684 | int64_t type = GetType(err); 685 | if (err.Fail()) return std::string(); 686 | 687 | if (type == v8()->types()->kHeapNumberType) { 688 | HeapNumber n(this); 689 | return n.ToString(false, err); 690 | } 691 | 692 | if (type < v8()->types()->kFirstNonstringType) { 693 | String str(this); 694 | return str.ToString(err); 695 | } 696 | 697 | return ""; 698 | } 699 | 700 | 701 | std::string HeapObject::Inspect(InspectOptions* options, Error& err) { 702 | int64_t type = GetType(err); 703 | if (err.Fail()) return std::string(); 704 | 705 | // TODO(indutny): make this configurable 706 | char buf[64]; 707 | if (options->print_map) { 708 | HeapObject map = GetMap(err); 709 | if (err.Fail()) return std::string(); 710 | 711 | snprintf(buf, sizeof(buf), "0x%016" PRIx64 "(map=0x%016" PRIx64 "):", raw(), 712 | map.raw()); 713 | } else { 714 | snprintf(buf, sizeof(buf), "0x%016" PRIx64 ":", raw()); 715 | } 716 | std::string pre = buf; 717 | 718 | if (type == v8()->types()->kGlobalObjectType) return pre + ""; 719 | if (type == v8()->types()->kCodeType) return pre + ""; 720 | if (type == v8()->types()->kMapType) { 721 | Map m(this); 722 | return pre + m.Inspect(options, err); 723 | } 724 | 725 | if (JSObject::IsObjectType(v8(), type)) { 726 | JSObject o(this); 727 | return pre + o.Inspect(options, err); 728 | } 729 | 730 | if (type == v8()->types()->kHeapNumberType) { 731 | HeapNumber n(this); 732 | return pre + n.Inspect(err); 733 | } 734 | 735 | if (type == v8()->types()->kJSArrayType) { 736 | JSArray arr(this); 737 | return pre + arr.Inspect(options, err); 738 | } 739 | 740 | if (type == v8()->types()->kOddballType) { 741 | Oddball o(this); 742 | return pre + o.Inspect(err); 743 | } 744 | 745 | if (type == v8()->types()->kJSFunctionType) { 746 | JSFunction fn(this); 747 | return pre + fn.Inspect(options, err); 748 | } 749 | 750 | if (type == v8()->types()->kJSRegExpType) { 751 | JSRegExp re(this); 752 | return pre + re.Inspect(options, err); 753 | } 754 | 755 | if (type < v8()->types()->kFirstNonstringType) { 756 | String str(this); 757 | return pre + str.Inspect(options, err); 758 | } 759 | 760 | if (type == v8()->types()->kFixedArrayType) { 761 | FixedArray arr(this); 762 | return pre + arr.Inspect(options, err); 763 | } 764 | 765 | if (type == v8()->types()->kJSArrayBufferType) { 766 | JSArrayBuffer buf(this); 767 | return pre + buf.Inspect(err); 768 | } 769 | 770 | if (type == v8()->types()->kJSTypedArrayType) { 771 | JSArrayBufferView view(this); 772 | return pre + view.Inspect(err); 773 | } 774 | 775 | if (type == v8()->types()->kJSDateType) { 776 | JSDate date(this); 777 | return pre + date.Inspect(err); 778 | } 779 | 780 | return pre + ""; 781 | } 782 | 783 | 784 | std::string Smi::ToString(Error& err) { 785 | char buf[128]; 786 | snprintf(buf, sizeof(buf), "%d", static_cast(GetValue())); 787 | err = Error::Ok(); 788 | return buf; 789 | } 790 | 791 | 792 | /* Utility function to generate short type names for objects. 793 | */ 794 | std::string HeapObject::GetTypeName(Error& err) { 795 | int64_t type = GetType(err); 796 | if (type == v8()->types()->kGlobalObjectType) return "(Global)"; 797 | if (type == v8()->types()->kCodeType) return "(Code)"; 798 | if (type == v8()->types()->kMapType) { 799 | return "(Map)"; 800 | } 801 | 802 | if (JSObject::IsObjectType(v8(), type)) { 803 | v8::HeapObject map_obj = GetMap(err); 804 | if (err.Fail()) { 805 | return std::string(); 806 | } 807 | 808 | v8::Map map(map_obj); 809 | v8::HeapObject constructor_obj = map.Constructor(err); 810 | if (err.Fail()) { 811 | return std::string(); 812 | } 813 | 814 | int64_t constructor_type = constructor_obj.GetType(err); 815 | if (err.Fail()) { 816 | return std::string(); 817 | } 818 | 819 | if (constructor_type != v8()->types()->kJSFunctionType) { 820 | return "(Object)"; 821 | } 822 | 823 | v8::JSFunction constructor(constructor_obj); 824 | 825 | return constructor.Name(err); 826 | } 827 | 828 | if (type == v8()->types()->kHeapNumberType) { 829 | return "(HeapNumber)"; 830 | } 831 | 832 | if (type == v8()->types()->kJSArrayType) { 833 | return "(Array)"; 834 | } 835 | 836 | if (type == v8()->types()->kOddballType) { 837 | return "(Oddball)"; 838 | } 839 | 840 | if (type == v8()->types()->kJSFunctionType) { 841 | return "(Function)"; 842 | } 843 | 844 | if (type == v8()->types()->kJSRegExpType) { 845 | return "(RegExp)"; 846 | } 847 | 848 | if (type < v8()->types()->kFirstNonstringType) { 849 | return "(String)"; 850 | } 851 | 852 | if (type == v8()->types()->kFixedArrayType) { 853 | return "(FixedArray)"; 854 | } 855 | 856 | if (type == v8()->types()->kJSArrayBufferType) { 857 | return "(ArrayBuffer)"; 858 | } 859 | 860 | if (type == v8()->types()->kJSTypedArrayType) { 861 | return "(ArrayBufferView)"; 862 | } 863 | 864 | if (type == v8()->types()->kJSDateType) { 865 | return "(Date)"; 866 | } 867 | 868 | std::string unknown("unknown: "); 869 | 870 | return unknown + std::to_string(type); 871 | } 872 | 873 | 874 | std::string Smi::Inspect(Error& err) { return ""; } 875 | 876 | 877 | std::string HeapNumber::ToString(bool whole, Error& err) { 878 | char buf[128]; 879 | const char* fmt = whole ? "%f" : "%.2f"; 880 | snprintf(buf, sizeof(buf), fmt, GetValue(err)); 881 | err = Error::Ok(); 882 | return buf; 883 | } 884 | 885 | 886 | std::string HeapNumber::Inspect(Error& err) { 887 | return ""; 888 | } 889 | 890 | 891 | std::string String::ToString(Error& err) { 892 | int64_t repr = Representation(err); 893 | if (err.Fail()) return std::string(); 894 | 895 | int64_t encoding = Encoding(err); 896 | if (err.Fail()) return std::string(); 897 | 898 | if (repr == v8()->string()->kSeqStringTag) { 899 | if (encoding == v8()->string()->kOneByteStringTag) { 900 | OneByteString one(this); 901 | return one.ToString(err); 902 | } else if (encoding == v8()->string()->kTwoByteStringTag) { 903 | TwoByteString two(this); 904 | return two.ToString(err); 905 | } 906 | 907 | err = Error::Failure("Unsupported seq string encoding"); 908 | return std::string(); 909 | } 910 | 911 | if (repr == v8()->string()->kConsStringTag) { 912 | ConsString cons(this); 913 | return cons.ToString(err); 914 | } 915 | 916 | if (repr == v8()->string()->kSlicedStringTag) { 917 | SlicedString sliced(this); 918 | return sliced.ToString(err); 919 | } 920 | 921 | // TODO(indutny): add support for external strings 922 | if (repr == v8()->string()->kExternalStringTag) { 923 | return std::string("(external)"); 924 | } 925 | 926 | err = Error::Failure("Unsupported string representation"); 927 | return std::string(); 928 | } 929 | 930 | 931 | std::string String::Inspect(InspectOptions* options, Error& err) { 932 | std::string val = ToString(err); 933 | if (err.Fail()) return std::string(); 934 | 935 | unsigned int len = options->string_length; 936 | 937 | if (len != 0 && val.length() > len) val = val.substr(0, len) + "..."; 938 | 939 | return ""; 940 | } 941 | 942 | 943 | std::string FixedArray::Inspect(InspectOptions* options, Error& err) { 944 | Smi length_smi = Length(err); 945 | if (err.Fail()) return std::string(); 946 | 947 | std::string res = ""; 956 | } 957 | 958 | 959 | std::string FixedArray::InspectContents(int length, Error& err) { 960 | std::string res; 961 | InspectOptions options; 962 | 963 | for (int i = 0; i < length; i++) { 964 | Value value = Get(i, err); 965 | if (err.Fail()) return std::string(); 966 | 967 | if (!res.empty()) res += ",\n"; 968 | 969 | char tmp[128]; 970 | snprintf(tmp, sizeof(tmp), " [%d]=", i); 971 | res += tmp + value.Inspect(&options, err); 972 | if (err.Fail()) return std::string(); 973 | } 974 | 975 | return res; 976 | } 977 | 978 | 979 | std::string Context::Inspect(Error& err) { 980 | std::string res; 981 | // Not enough postmortem information, return bare minimum 982 | if (v8()->shared_info()->kScopeInfoOffset == -1) return res; 983 | 984 | Value previous = Previous(err); 985 | if (err.Fail()) return std::string(); 986 | 987 | JSFunction closure = Closure(err); 988 | if (err.Fail()) return std::string(); 989 | 990 | SharedFunctionInfo info = closure.Info(err); 991 | if (err.Fail()) return std::string(); 992 | 993 | HeapObject scope_obj = info.GetScopeInfo(err); 994 | if (err.Fail()) return std::string(); 995 | 996 | ScopeInfo scope(scope_obj); 997 | 998 | Smi param_count_smi = scope.ParameterCount(err); 999 | if (err.Fail()) return std::string(); 1000 | Smi stack_count_smi = scope.StackLocalCount(err); 1001 | if (err.Fail()) return std::string(); 1002 | Smi local_count_smi = scope.ContextLocalCount(err); 1003 | if (err.Fail()) return std::string(); 1004 | 1005 | InspectOptions options; 1006 | 1007 | HeapObject heap_previous = HeapObject(previous); 1008 | if (heap_previous.Check()) { 1009 | char tmp[128]; 1010 | snprintf(tmp, sizeof(tmp), " (previous)=0x%016" PRIx64, previous.raw()); 1011 | res += tmp; 1012 | } 1013 | 1014 | if (!res.empty()) res += "\n"; 1015 | { 1016 | char tmp[128]; 1017 | snprintf(tmp, sizeof(tmp), " (closure)=0x%016" PRIx64 " {", 1018 | closure.raw()); 1019 | res += tmp; 1020 | 1021 | InspectOptions options; 1022 | res += closure.Inspect(&options, err) + "}"; 1023 | if (err.Fail()) return std::string(); 1024 | } 1025 | 1026 | int param_count = param_count_smi.GetValue(); 1027 | int stack_count = stack_count_smi.GetValue(); 1028 | int local_count = local_count_smi.GetValue(); 1029 | for (int i = 0; i < local_count; i++) { 1030 | String name = scope.ContextLocalName(i, param_count, stack_count, err); 1031 | if (err.Fail()) return std::string(); 1032 | 1033 | if (!res.empty()) res += ",\n"; 1034 | 1035 | res += " " + name.ToString(err) + "="; 1036 | if (err.Fail()) return std::string(); 1037 | 1038 | Value value = ContextSlot(i, err); 1039 | if (err.Fail()) return std::string(); 1040 | 1041 | res += value.Inspect(&options, err); 1042 | if (err.Fail()) return std::string(); 1043 | } 1044 | 1045 | return res; 1046 | } 1047 | 1048 | 1049 | std::string Oddball::Inspect(Error& err) { 1050 | Smi kind = Kind(err); 1051 | if (err.Fail()) return std::string(); 1052 | 1053 | int64_t kind_val = kind.GetValue(); 1054 | if (kind_val == v8()->oddball()->kException) return ""; 1055 | if (kind_val == v8()->oddball()->kFalse) return ""; 1056 | if (kind_val == v8()->oddball()->kTrue) return ""; 1057 | if (kind_val == v8()->oddball()->kUndefined) return ""; 1058 | if (kind_val == v8()->oddball()->kNull) return ""; 1059 | if (kind_val == v8()->oddball()->kTheHole) return ""; 1060 | if (kind_val == v8()->oddball()->kUninitialized) return ""; 1061 | return ""; 1062 | } 1063 | 1064 | 1065 | std::string JSArrayBuffer::Inspect(Error& err) { 1066 | bool neutered = WasNeutered(err); 1067 | if (err.Fail()) return std::string(); 1068 | 1069 | if (neutered) return ""; 1070 | 1071 | int64_t data = BackingStore(err); 1072 | if (err.Fail()) return std::string(); 1073 | 1074 | Smi length = ByteLength(err); 1075 | if (err.Fail()) return std::string(); 1076 | 1077 | char tmp[128]; 1078 | snprintf(tmp, sizeof(tmp), "", data, 1079 | static_cast(length.GetValue())); 1080 | return tmp; 1081 | } 1082 | 1083 | 1084 | std::string JSArrayBufferView::Inspect(Error& err) { 1085 | JSArrayBuffer buf = Buffer(err); 1086 | if (err.Fail()) return std::string(); 1087 | 1088 | bool neutered = buf.WasNeutered(err); 1089 | if (err.Fail()) return std::string(); 1090 | 1091 | if (neutered) return ""; 1092 | 1093 | int64_t data = buf.BackingStore(err); 1094 | if (err.Fail()) return std::string(); 1095 | 1096 | Smi off = ByteOffset(err); 1097 | if (err.Fail()) return std::string(); 1098 | 1099 | Smi length = ByteLength(err); 1100 | if (err.Fail()) return std::string(); 1101 | 1102 | char tmp[128]; 1103 | snprintf(tmp, sizeof(tmp), "", data, 1104 | static_cast(off.GetValue()), 1105 | static_cast(length.GetValue())); 1106 | return tmp; 1107 | } 1108 | 1109 | 1110 | std::string Map::Inspect(InspectOptions* options, Error& err) { 1111 | HeapObject descriptors_obj = InstanceDescriptors(err); 1112 | if (err.Fail()) return std::string(); 1113 | 1114 | int64_t own_descriptors_count = NumberOfOwnDescriptors(err); 1115 | if (err.Fail()) return std::string(); 1116 | 1117 | int64_t in_object_properties = InObjectProperties(err); 1118 | if (err.Fail()) return std::string(); 1119 | 1120 | int64_t instance_size = InstanceSize(err); 1121 | if (err.Fail()) return std::string(); 1122 | 1123 | char tmp[256]; 1124 | snprintf(tmp, sizeof(tmp), 1125 | "(own_descriptors_count), 1128 | static_cast(in_object_properties), 1129 | static_cast(instance_size), descriptors_obj.raw()); 1130 | if (!options->detailed) { 1131 | return std::string(tmp) + ">"; 1132 | } 1133 | 1134 | DescriptorArray descriptors(descriptors_obj); 1135 | if (err.Fail()) return std::string(); 1136 | 1137 | return std::string(tmp) + ":" + descriptors.Inspect(options, err) + ">"; 1138 | } 1139 | 1140 | 1141 | HeapObject Map::Constructor(Error& err) { 1142 | Map current = this; 1143 | 1144 | do { 1145 | HeapObject obj = current.MaybeConstructor(err); 1146 | if (err.Fail()) return current; 1147 | 1148 | int64_t type = obj.GetType(err); 1149 | if (err.Fail()) return current; 1150 | 1151 | current = obj; 1152 | if (type != v8()->types()->kMapType) break; 1153 | } while (true); 1154 | 1155 | return current; 1156 | } 1157 | 1158 | 1159 | std::string JSObject::Inspect(InspectOptions* options, Error& err) { 1160 | HeapObject map_obj = GetMap(err); 1161 | if (err.Fail()) return std::string(); 1162 | 1163 | Map map(map_obj); 1164 | HeapObject constructor_obj = map.Constructor(err); 1165 | if (err.Fail()) return std::string(); 1166 | 1167 | int64_t constructor_type = constructor_obj.GetType(err); 1168 | if (err.Fail()) return std::string(); 1169 | 1170 | if (constructor_type != v8()->types()->kJSFunctionType) 1171 | return ""; 1172 | 1173 | JSFunction constructor(constructor_obj); 1174 | 1175 | std::string res = "detailed) { 1180 | res += " " + InspectProperties(err); 1181 | if (err.Fail()) return std::string(); 1182 | 1183 | std::string fields = InspectInternalFields(err); 1184 | if (err.Fail()) return std::string(); 1185 | 1186 | if (!fields.empty()) res += "\n internal fields {\n" + fields + "}"; 1187 | } 1188 | 1189 | res += ">"; 1190 | return res; 1191 | } 1192 | 1193 | 1194 | std::string JSObject::InspectInternalFields(Error& err) { 1195 | HeapObject map_obj = GetMap(err); 1196 | if (err.Fail()) return std::string(); 1197 | 1198 | Map map(map_obj); 1199 | int64_t type = map.GetType(err); 1200 | if (err.Fail()) return std::string(); 1201 | 1202 | // Only JSObject for now 1203 | if (!JSObject::IsObjectType(v8(), type)) return std::string(); 1204 | 1205 | int64_t instance_size = map.InstanceSize(err); 1206 | 1207 | // kVariableSizeSentinel == 0 1208 | // TODO(indutny): post-mortem constant for this? 1209 | if (err.Fail() || instance_size == 0) return std::string(); 1210 | 1211 | int64_t in_object_props = map.InObjectProperties(err); 1212 | if (err.Fail()) return std::string(); 1213 | 1214 | // in-object properties are appended to the end of the JSObject, 1215 | // skip them. 1216 | instance_size -= in_object_props * v8()->common()->kPointerSize; 1217 | 1218 | std::string res; 1219 | for (int64_t off = v8()->js_object()->kInternalFieldsOffset; 1220 | off < instance_size; off += v8()->common()->kPointerSize) { 1221 | int64_t field = LoadField(off, err); 1222 | if (err.Fail()) return std::string(); 1223 | 1224 | char tmp[128]; 1225 | snprintf(tmp, sizeof(tmp), " 0x%016" PRIx64, field); 1226 | 1227 | if (!res.empty()) res += ",\n "; 1228 | res += tmp; 1229 | } 1230 | 1231 | return res; 1232 | } 1233 | 1234 | 1235 | std::string JSObject::InspectProperties(Error& err) { 1236 | std::string res; 1237 | 1238 | std::string elems = InspectElements(err); 1239 | if (err.Fail()) return std::string(); 1240 | 1241 | if (!elems.empty()) res = "elements {\n" + elems + "}"; 1242 | 1243 | HeapObject map_obj = GetMap(err); 1244 | if (err.Fail()) return std::string(); 1245 | 1246 | Map map(map_obj); 1247 | 1248 | std::string props; 1249 | bool is_dict = map.IsDictionary(err); 1250 | if (err.Fail()) return std::string(); 1251 | 1252 | if (is_dict) 1253 | props = InspectDictionary(err); 1254 | else 1255 | props = InspectDescriptors(map, err); 1256 | 1257 | if (err.Fail()) return std::string(); 1258 | 1259 | if (!props.empty()) { 1260 | if (!res.empty()) res += "\n "; 1261 | res += "properties {\n" + props + "}"; 1262 | } 1263 | 1264 | return res; 1265 | } 1266 | 1267 | 1268 | std::string JSObject::InspectElements(Error& err) { 1269 | HeapObject elements_obj = Elements(err); 1270 | if (err.Fail()) return std::string(); 1271 | 1272 | FixedArray elements(elements_obj); 1273 | 1274 | Smi length_smi = elements.Length(err); 1275 | if (err.Fail()) return std::string(); 1276 | 1277 | InspectOptions options; 1278 | 1279 | int64_t length = length_smi.GetValue(); 1280 | std::string res; 1281 | for (int64_t i = 0; i < length; i++) { 1282 | Value value = elements.Get(i, err); 1283 | if (err.Fail()) return std::string(); 1284 | 1285 | bool is_hole = value.IsHole(err); 1286 | if (err.Fail()) return std::string(); 1287 | 1288 | // Skip holes 1289 | if (is_hole) continue; 1290 | 1291 | if (!res.empty()) res += ",\n"; 1292 | 1293 | char tmp[64]; 1294 | snprintf(tmp, sizeof(tmp), " [%d]=", static_cast(i)); 1295 | res += tmp; 1296 | 1297 | res += value.Inspect(&options, err); 1298 | if (err.Fail()) return std::string(); 1299 | } 1300 | 1301 | return res; 1302 | } 1303 | 1304 | 1305 | std::string JSObject::InspectDictionary(Error& err) { 1306 | HeapObject dictionary_obj = Properties(err); 1307 | if (err.Fail()) return std::string(); 1308 | 1309 | NameDictionary dictionary(dictionary_obj); 1310 | 1311 | int64_t length = dictionary.Length(err); 1312 | if (err.Fail()) return std::string(); 1313 | 1314 | InspectOptions options; 1315 | 1316 | std::string res; 1317 | for (int64_t i = 0; i < length; i++) { 1318 | Value key = dictionary.GetKey(i, err); 1319 | if (err.Fail()) return std::string(); 1320 | 1321 | // Skip holes 1322 | bool is_hole = key.IsHoleOrUndefined(err); 1323 | if (err.Fail()) return std::string(); 1324 | if (is_hole) continue; 1325 | 1326 | Value value = dictionary.GetValue(i, err); 1327 | if (err.Fail()) return std::string(); 1328 | 1329 | if (!res.empty()) res += ",\n"; 1330 | 1331 | res += " ." + key.ToString(err) + "="; 1332 | if (err.Fail()) return std::string(); 1333 | 1334 | res += value.Inspect(&options, err); 1335 | if (err.Fail()) return std::string(); 1336 | } 1337 | 1338 | return res; 1339 | } 1340 | 1341 | 1342 | std::string JSObject::InspectDescriptors(Map map, Error& err) { 1343 | HeapObject descriptors_obj = map.InstanceDescriptors(err); 1344 | if (err.Fail()) return std::string(); 1345 | 1346 | DescriptorArray descriptors(descriptors_obj); 1347 | int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); 1348 | if (err.Fail()) return std::string(); 1349 | 1350 | int64_t in_object_count = map.InObjectProperties(err); 1351 | if (err.Fail()) return std::string(); 1352 | 1353 | int64_t instance_size = map.InstanceSize(err); 1354 | if (err.Fail()) return std::string(); 1355 | 1356 | HeapObject extra_properties_obj = Properties(err); 1357 | if (err.Fail()) return std::string(); 1358 | 1359 | FixedArray extra_properties(extra_properties_obj); 1360 | 1361 | InspectOptions options; 1362 | 1363 | std::string res; 1364 | for (int64_t i = 0; i < own_descriptors_count; i++) { 1365 | Smi details = descriptors.GetDetails(i, err); 1366 | if (err.Fail()) return std::string(); 1367 | 1368 | Value key = descriptors.GetKey(i, err); 1369 | if (err.Fail()) return std::string(); 1370 | 1371 | if (!res.empty()) res += ",\n"; 1372 | 1373 | res += " ." + key.ToString(err) + "="; 1374 | if (err.Fail()) return std::string(); 1375 | 1376 | if (descriptors.IsConstFieldDetails(details)) { 1377 | Value value; 1378 | 1379 | value = descriptors.GetValue(i, err); 1380 | if (err.Fail()) return std::string(); 1381 | 1382 | res += value.Inspect(&options, err); 1383 | if (err.Fail()) return std::string(); 1384 | continue; 1385 | } 1386 | 1387 | // Skip non-fields for now 1388 | if (!descriptors.IsFieldDetails(details)) { 1389 | res += ""; 1390 | continue; 1391 | } 1392 | 1393 | int64_t index = descriptors.FieldIndex(details) - in_object_count; 1394 | 1395 | if (descriptors.IsDoubleField(details)) { 1396 | double value; 1397 | if (index < 0) 1398 | value = GetInObjectValue(instance_size, index, err); 1399 | else 1400 | value = extra_properties.Get(index, err); 1401 | 1402 | if (err.Fail()) return std::string(); 1403 | 1404 | char tmp[100]; 1405 | snprintf(tmp, sizeof(tmp), "%f", value); 1406 | res += tmp; 1407 | } else { 1408 | Value value; 1409 | if (index < 0) 1410 | value = GetInObjectValue(instance_size, index, err); 1411 | else 1412 | value = extra_properties.Get(index, err); 1413 | 1414 | if (err.Fail()) return std::string(); 1415 | 1416 | res += value.Inspect(&options, err); 1417 | } 1418 | if (err.Fail()) return std::string(); 1419 | } 1420 | 1421 | return res; 1422 | } 1423 | 1424 | 1425 | template 1426 | T JSObject::GetInObjectValue(int64_t size, int index, Error& err) { 1427 | return LoadFieldValue(size + index * v8()->common()->kPointerSize, err); 1428 | } 1429 | 1430 | 1431 | /* Returns the set of keys on an object - similar to Object.keys(obj) in 1432 | * Javascript. That includes array indices but not special fields like 1433 | * "length" on an array. 1434 | */ 1435 | void JSObject::Keys(std::vector& keys, Error& err) { 1436 | keys.clear(); 1437 | 1438 | // First handle array indices. 1439 | ElementKeys(keys, err); 1440 | 1441 | HeapObject map_obj = GetMap(err); 1442 | 1443 | Map map(map_obj); 1444 | 1445 | bool is_dict = map.IsDictionary(err); 1446 | if (err.Fail()) return; 1447 | 1448 | if (is_dict) { 1449 | DictionaryKeys(keys, err); 1450 | } else { 1451 | DescriptorKeys(keys, map, err); 1452 | } 1453 | 1454 | return; 1455 | } 1456 | 1457 | 1458 | std::vector> JSObject::Entries(Error& err) { 1459 | HeapObject map_obj = GetMap(err); 1460 | 1461 | Map map(map_obj); 1462 | 1463 | bool is_dict = map.IsDictionary(err); 1464 | if (err.Fail()) return {}; 1465 | 1466 | if (is_dict) { 1467 | return DictionaryEntries(err); 1468 | } else { 1469 | return DescriptorEntries(map, err); 1470 | } 1471 | } 1472 | 1473 | 1474 | std::vector> JSObject::DictionaryEntries(Error& err) { 1475 | HeapObject dictionary_obj = Properties(err); 1476 | if (err.Fail()) return {}; 1477 | 1478 | NameDictionary dictionary(dictionary_obj); 1479 | 1480 | int64_t length = dictionary.Length(err); 1481 | if (err.Fail()) return {}; 1482 | 1483 | std::vector> entries; 1484 | for (int64_t i = 0; i < length; i++) { 1485 | Value key = dictionary.GetKey(i, err); 1486 | 1487 | if (err.Fail()) return entries; 1488 | 1489 | // Skip holes 1490 | bool is_hole = key.IsHoleOrUndefined(err); 1491 | if (err.Fail()) return entries; 1492 | if (is_hole) continue; 1493 | 1494 | Value value = dictionary.GetValue(i, err); 1495 | 1496 | entries.push_back(std::pair(key, value)); 1497 | } 1498 | return entries; 1499 | } 1500 | 1501 | 1502 | std::vector> JSObject::DescriptorEntries(Map map, 1503 | Error& err) { 1504 | HeapObject descriptors_obj = map.InstanceDescriptors(err); 1505 | if (err.Fail()) return {}; 1506 | 1507 | DescriptorArray descriptors(descriptors_obj); 1508 | 1509 | int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); 1510 | if (err.Fail()) return {}; 1511 | 1512 | int64_t in_object_count = map.InObjectProperties(err); 1513 | if (err.Fail()) return {}; 1514 | 1515 | int64_t instance_size = map.InstanceSize(err); 1516 | if (err.Fail()) return {}; 1517 | 1518 | HeapObject extra_properties_obj = Properties(err); 1519 | if (err.Fail()) return {}; 1520 | 1521 | FixedArray extra_properties(extra_properties_obj); 1522 | 1523 | std::vector> entries; 1524 | for (int64_t i = 0; i < own_descriptors_count; i++) { 1525 | Smi details = descriptors.GetDetails(i, err); 1526 | if (err.Fail()) continue; 1527 | 1528 | Value key = descriptors.GetKey(i, err); 1529 | if (err.Fail()) continue; 1530 | 1531 | if (descriptors.IsConstFieldDetails(details)) { 1532 | Value value; 1533 | 1534 | value = descriptors.GetValue(i, err); 1535 | if (err.Fail()) continue; 1536 | 1537 | entries.push_back(std::pair(key, value)); 1538 | continue; 1539 | } 1540 | 1541 | // Skip non-fields for now, Object.keys(obj) does 1542 | // not seem to return these (for example the "length" 1543 | // field on an array). 1544 | if (!descriptors.IsFieldDetails(details)) continue; 1545 | 1546 | if (descriptors.IsDoubleField(details)) continue; 1547 | 1548 | int64_t index = descriptors.FieldIndex(details) - in_object_count; 1549 | 1550 | Value value; 1551 | if (index < 0) { 1552 | value = GetInObjectValue(instance_size, index, err); 1553 | } else { 1554 | value = extra_properties.Get(index, err); 1555 | } 1556 | 1557 | entries.push_back(std::pair(key, value)); 1558 | } 1559 | 1560 | return entries; 1561 | } 1562 | 1563 | 1564 | void JSObject::ElementKeys(std::vector& keys, Error& err) { 1565 | HeapObject elements_obj = Elements(err); 1566 | if (err.Fail()) return; 1567 | 1568 | FixedArray elements(elements_obj); 1569 | 1570 | Smi length_smi = elements.Length(err); 1571 | if (err.Fail()) return; 1572 | 1573 | int64_t length = length_smi.GetValue(); 1574 | for (int i = 0; i < length; ++i) { 1575 | // Add keys for anything that isn't a hole. 1576 | Value value = elements.Get(i, err); 1577 | if (err.Fail()) continue; 1578 | ; 1579 | 1580 | bool is_hole = value.IsHole(err); 1581 | if (err.Fail()) continue; 1582 | if (!is_hole) { 1583 | keys.push_back(std::to_string(i)); 1584 | } 1585 | } 1586 | } 1587 | 1588 | void JSObject::DictionaryKeys(std::vector& keys, Error& err) { 1589 | HeapObject dictionary_obj = Properties(err); 1590 | if (err.Fail()) return; 1591 | 1592 | NameDictionary dictionary(dictionary_obj); 1593 | 1594 | int64_t length = dictionary.Length(err); 1595 | if (err.Fail()) return; 1596 | 1597 | for (int64_t i = 0; i < length; i++) { 1598 | Value key = dictionary.GetKey(i, err); 1599 | if (err.Fail()) return; 1600 | 1601 | // Skip holes 1602 | bool is_hole = key.IsHoleOrUndefined(err); 1603 | if (err.Fail()) return; 1604 | if (is_hole) continue; 1605 | 1606 | std::string key_name = key.ToString(err); 1607 | if (err.Fail()) { 1608 | // TODO - should I continue onto the next key here instead. 1609 | return; 1610 | } 1611 | 1612 | keys.push_back(key_name); 1613 | } 1614 | } 1615 | 1616 | void JSObject::DescriptorKeys(std::vector& keys, Map map, 1617 | Error& err) { 1618 | HeapObject descriptors_obj = map.InstanceDescriptors(err); 1619 | if (err.Fail()) return; 1620 | 1621 | DescriptorArray descriptors(descriptors_obj); 1622 | int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); 1623 | if (err.Fail()) return; 1624 | 1625 | for (int64_t i = 0; i < own_descriptors_count; i++) { 1626 | Smi details = descriptors.GetDetails(i, err); 1627 | if (err.Fail()) return; 1628 | 1629 | Value key = descriptors.GetKey(i, err); 1630 | if (err.Fail()) return; 1631 | 1632 | // Skip non-fields for now, Object.keys(obj) does 1633 | // not seem to return these (for example the "length" 1634 | // field on an array). 1635 | if (!descriptors.IsFieldDetails(details)) { 1636 | continue; 1637 | } 1638 | 1639 | std::string key_name = key.ToString(err); 1640 | if (err.Fail()) { 1641 | // TODO - should I continue onto the next key here instead. 1642 | return; 1643 | } 1644 | 1645 | keys.push_back(key_name); 1646 | } 1647 | } 1648 | 1649 | /* Return the v8 value for a property stored using the given key. 1650 | * (Caller should have some idea of what type of object will be stored 1651 | * in that key, they will get a v8::Value back that they can cast.) 1652 | */ 1653 | Value JSObject::GetProperty(std::string key_name, Error& err) { 1654 | HeapObject map_obj = GetMap(err); 1655 | if (err.Fail()) Value(); 1656 | 1657 | Map map(map_obj); 1658 | 1659 | bool is_dict = map.IsDictionary(err); 1660 | if (err.Fail()) return Value(); 1661 | 1662 | if (is_dict) { 1663 | return GetDictionaryProperty(key_name, err); 1664 | } else { 1665 | return GetDescriptorProperty(key_name, map, err); 1666 | } 1667 | 1668 | if (err.Fail()) return Value(); 1669 | 1670 | // Nothing's gone wrong, we just didn't find the key. 1671 | return Value(); 1672 | } 1673 | 1674 | Value JSObject::GetDictionaryProperty(std::string key_name, Error& err) { 1675 | HeapObject dictionary_obj = Properties(err); 1676 | if (err.Fail()) return Value(); 1677 | 1678 | NameDictionary dictionary(dictionary_obj); 1679 | 1680 | int64_t length = dictionary.Length(err); 1681 | if (err.Fail()) return Value(); 1682 | 1683 | for (int64_t i = 0; i < length; i++) { 1684 | Value key = dictionary.GetKey(i, err); 1685 | if (err.Fail()) return Value(); 1686 | 1687 | // Skip holes 1688 | bool is_hole = key.IsHoleOrUndefined(err); 1689 | if (err.Fail()) return Value(); 1690 | if (is_hole) continue; 1691 | 1692 | if (key.ToString(err) == key_name) { 1693 | Value value = dictionary.GetValue(i, err); 1694 | 1695 | if (err.Fail()) return Value(); 1696 | 1697 | return value; 1698 | } 1699 | } 1700 | return Value(); 1701 | } 1702 | 1703 | Value JSObject::GetDescriptorProperty(std::string key_name, Map map, 1704 | Error& err) { 1705 | HeapObject descriptors_obj = map.InstanceDescriptors(err); 1706 | if (err.Fail()) return Value(); 1707 | 1708 | DescriptorArray descriptors(descriptors_obj); 1709 | int64_t own_descriptors_count = map.NumberOfOwnDescriptors(err); 1710 | if (err.Fail()) return Value(); 1711 | 1712 | int64_t in_object_count = map.InObjectProperties(err); 1713 | if (err.Fail()) return Value(); 1714 | 1715 | int64_t instance_size = map.InstanceSize(err); 1716 | if (err.Fail()) return Value(); 1717 | 1718 | HeapObject extra_properties_obj = Properties(err); 1719 | if (err.Fail()) return Value(); 1720 | 1721 | FixedArray extra_properties(extra_properties_obj); 1722 | 1723 | for (int64_t i = 0; i < own_descriptors_count; i++) { 1724 | Smi details = descriptors.GetDetails(i, err); 1725 | if (err.Fail()) return Value(); 1726 | 1727 | Value key = descriptors.GetKey(i, err); 1728 | if (err.Fail()) return Value(); 1729 | 1730 | if (key.ToString(err) != key_name) { 1731 | continue; 1732 | } 1733 | 1734 | // Found the right key, get the value. 1735 | if (err.Fail()) return Value(); 1736 | 1737 | if (descriptors.IsConstFieldDetails(details)) { 1738 | Value value; 1739 | 1740 | value = descriptors.GetValue(i, err); 1741 | if (err.Fail()) return Value(); 1742 | 1743 | continue; 1744 | } 1745 | 1746 | // Skip non-fields for now 1747 | if (!descriptors.IsFieldDetails(details)) { 1748 | // This path would return the length field for an array, 1749 | // however Object.keys(arr) doesn't return length as a 1750 | // field so neither do we. 1751 | continue; 1752 | } 1753 | 1754 | int64_t index = descriptors.FieldIndex(details) - in_object_count; 1755 | 1756 | if (descriptors.IsDoubleField(details)) { 1757 | double value; 1758 | if (index < 0) { 1759 | value = GetInObjectValue(instance_size, index, err); 1760 | } else { 1761 | value = extra_properties.Get(index, err); 1762 | } 1763 | 1764 | if (err.Fail()) return Value(); 1765 | 1766 | } else { 1767 | Value value; 1768 | if (index < 0) { 1769 | value = GetInObjectValue(instance_size, index, err); 1770 | } else { 1771 | value = extra_properties.Get(index, err); 1772 | } 1773 | 1774 | if (err.Fail()) { 1775 | return Value(); 1776 | } else { 1777 | return value; 1778 | }; 1779 | } 1780 | if (err.Fail()) return Value(); 1781 | } 1782 | return Value(); 1783 | } 1784 | 1785 | 1786 | /* An array is also an object so this method is on JSObject 1787 | * not JSArray. 1788 | */ 1789 | int64_t JSObject::GetArrayLength(Error& err) { 1790 | HeapObject elements_obj = Elements(err); 1791 | if (err.Fail()) return 0; 1792 | 1793 | FixedArray elements(elements_obj); 1794 | Smi length_smi = elements.Length(err); 1795 | if (err.Fail()) return 0; 1796 | 1797 | int64_t length = length_smi.GetValue(); 1798 | return length; 1799 | } 1800 | 1801 | 1802 | /* An array is also an object so this method is on JSObject 1803 | * not JSArray. 1804 | * Note that you the user should know what the expect the array to contain 1805 | * and should check they haven't been returned a hole. 1806 | */ 1807 | v8::Value JSObject::GetArrayElement(int64_t pos, Error& err) { 1808 | if (pos < 0) { 1809 | // TODO - Set err.Fail()? 1810 | return Value(); 1811 | } 1812 | 1813 | HeapObject elements_obj = Elements(err); 1814 | if (err.Fail()) return Value(); 1815 | 1816 | FixedArray elements(elements_obj); 1817 | Smi length_smi = elements.Length(err); 1818 | if (err.Fail()) return Value(); 1819 | 1820 | int64_t length = length_smi.GetValue(); 1821 | if (pos >= length) { 1822 | return Value(); 1823 | } 1824 | 1825 | Value value = elements.Get(pos, err); 1826 | if (err.Fail()) return Value(); 1827 | 1828 | return value; 1829 | } 1830 | 1831 | std::string JSArray::Inspect(InspectOptions* options, Error& err) { 1832 | Smi length = Length(err); 1833 | if (err.Fail()) return std::string(); 1834 | 1835 | std::string res = ""; 1844 | } 1845 | 1846 | } // namespace v8 1847 | } // namespace llnode 1848 | -------------------------------------------------------------------------------- /src/llv8.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LLV8_H_ 2 | #define SRC_LLV8_H_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "src/llv8-constants.h" 9 | 10 | namespace llnode { 11 | 12 | class FindJSObjectsVisitor; 13 | class FindReferencesCmd; 14 | 15 | namespace v8 { 16 | 17 | // Forward declarations 18 | class LLV8; 19 | class CodeMap; 20 | 21 | class Error { 22 | public: 23 | Error() : failed_(false), msg_(nullptr) {} 24 | Error(bool failed, const char* msg) : failed_(failed), msg_(msg) {} 25 | 26 | static inline Error Ok() { return Error(false, "ok"); } 27 | static inline Error Failure(const char* msg) { return Error(true, msg); } 28 | 29 | inline bool Success() const { return !Fail(); } 30 | inline bool Fail() const { return failed_; } 31 | 32 | inline const char* GetMessage() { return msg_; } 33 | 34 | private: 35 | bool failed_; 36 | const char* msg_; 37 | }; 38 | 39 | #define V8_VALUE_DEFAULT_METHODS(NAME, PARENT) \ 40 | NAME(const NAME& v) = default; \ 41 | NAME() : PARENT() {} \ 42 | NAME(LLV8* v8, int64_t raw) : PARENT(v8, raw) {} \ 43 | NAME(Value& v) : PARENT(v) {} \ 44 | NAME(Value* v) : PARENT(v->v8(), v->raw()) {} 45 | 46 | class Value { 47 | public: 48 | class InspectOptions { 49 | public: 50 | InspectOptions() 51 | : detailed(false), 52 | print_map(false), 53 | print_source(false), 54 | string_length(kStringLength) {} 55 | 56 | static const unsigned int kStringLength = 16; 57 | 58 | bool detailed; 59 | bool print_map; 60 | bool print_source; 61 | unsigned int string_length; 62 | }; 63 | 64 | Value(const Value& v) = default; 65 | Value(Value& v) = default; 66 | Value() : v8_(nullptr), raw_(-1) {} 67 | Value(LLV8* v8, int64_t raw) : v8_(v8), raw_(raw) {} 68 | 69 | inline bool Check() const { return true; } 70 | 71 | inline int64_t raw() const { return raw_; } 72 | inline LLV8* v8() const { return v8_; } 73 | 74 | bool IsHoleOrUndefined(Error& err); 75 | bool IsHole(Error& err); 76 | 77 | std::string Inspect(InspectOptions* options, Error& err); 78 | std::string GetTypeName(Error& err); 79 | std::string ToString(Error& err); 80 | 81 | protected: 82 | LLV8* v8_; 83 | int64_t raw_; 84 | }; 85 | 86 | class Smi : public Value { 87 | public: 88 | V8_VALUE_DEFAULT_METHODS(Smi, Value) 89 | 90 | inline bool Check() const; 91 | inline int64_t GetValue() const; 92 | 93 | std::string ToString(Error& err); 94 | std::string Inspect(Error& err); 95 | }; 96 | 97 | class HeapObject : public Value { 98 | public: 99 | V8_VALUE_DEFAULT_METHODS(HeapObject, Value) 100 | 101 | inline bool Check() const; 102 | inline int64_t LeaField(int64_t off) const; 103 | inline int64_t LoadField(int64_t off, Error& err); 104 | 105 | template 106 | inline T LoadFieldValue(int64_t off, Error& err); 107 | 108 | inline HeapObject GetMap(Error& err); 109 | inline int64_t GetType(Error& err); 110 | 111 | std::string ToString(Error& err); 112 | std::string Inspect(InspectOptions* options, Error& err); 113 | std::string GetTypeName(Error& err); 114 | }; 115 | 116 | class Map : public HeapObject { 117 | public: 118 | V8_VALUE_DEFAULT_METHODS(Map, HeapObject) 119 | 120 | inline int64_t GetType(Error& err); 121 | inline HeapObject MaybeConstructor(Error& err); 122 | inline HeapObject InstanceDescriptors(Error& err); 123 | inline int64_t BitField3(Error& err); 124 | inline int64_t InObjectProperties(Error& err); 125 | inline int64_t InstanceSize(Error& err); 126 | 127 | inline bool IsDictionary(Error& err); 128 | inline int64_t NumberOfOwnDescriptors(Error& err); 129 | 130 | std::string Inspect(InspectOptions* options, Error& err); 131 | HeapObject Constructor(Error& err); 132 | }; 133 | 134 | class String : public HeapObject { 135 | public: 136 | V8_VALUE_DEFAULT_METHODS(String, HeapObject) 137 | 138 | inline int64_t Encoding(Error& err); 139 | inline int64_t Representation(Error& err); 140 | inline Smi Length(Error& err); 141 | 142 | std::string ToString(Error& err); 143 | std::string Inspect(InspectOptions* options, Error& err); 144 | }; 145 | 146 | class Script : public HeapObject { 147 | public: 148 | V8_VALUE_DEFAULT_METHODS(Script, HeapObject) 149 | 150 | inline String Name(Error& err); 151 | inline Smi LineOffset(Error& err); 152 | inline HeapObject Source(Error& err); 153 | inline HeapObject LineEnds(Error& err); 154 | 155 | void GetLines(uint64_t start_line, std::string lines[], uint64_t line_limit, 156 | uint32_t& lines_found, Error& err); 157 | void GetLineColumnFromPos(int64_t pos, int64_t& line, int64_t& column, 158 | Error& err); 159 | }; 160 | 161 | class Code : public HeapObject { 162 | public: 163 | V8_VALUE_DEFAULT_METHODS(Code, HeapObject) 164 | 165 | inline int64_t Start(); 166 | inline int64_t Size(Error& err); 167 | }; 168 | 169 | class SharedFunctionInfo : public HeapObject { 170 | public: 171 | V8_VALUE_DEFAULT_METHODS(SharedFunctionInfo, HeapObject) 172 | 173 | inline String Name(Error& err); 174 | inline String InferredName(Error& err); 175 | inline Script GetScript(Error& err); 176 | inline Code GetCode(Error& err); 177 | inline HeapObject GetScopeInfo(Error& err); 178 | inline int64_t ParameterCount(Error& err); 179 | inline int64_t StartPosition(Error& err); 180 | inline int64_t EndPosition(Error& err); 181 | 182 | std::string ProperName(Error& err); 183 | std::string GetPostfix(Error& err); 184 | std::string ToString(Error& err); 185 | }; 186 | 187 | class OneByteString : public String { 188 | public: 189 | V8_VALUE_DEFAULT_METHODS(OneByteString, String) 190 | 191 | inline std::string ToString(Error& err); 192 | }; 193 | 194 | class TwoByteString : public String { 195 | public: 196 | V8_VALUE_DEFAULT_METHODS(TwoByteString, String) 197 | 198 | inline std::string ToString(Error& err); 199 | }; 200 | 201 | class ConsString : public String { 202 | public: 203 | V8_VALUE_DEFAULT_METHODS(ConsString, String) 204 | 205 | inline String First(Error& err); 206 | inline String Second(Error& err); 207 | 208 | inline std::string ToString(Error& err); 209 | }; 210 | 211 | class SlicedString : public String { 212 | public: 213 | V8_VALUE_DEFAULT_METHODS(SlicedString, String) 214 | 215 | inline String Parent(Error& err); 216 | inline Smi Offset(Error& err); 217 | 218 | inline std::string ToString(Error& err); 219 | }; 220 | 221 | class HeapNumber : public HeapObject { 222 | public: 223 | V8_VALUE_DEFAULT_METHODS(HeapNumber, HeapObject) 224 | 225 | inline double GetValue(Error& err); 226 | 227 | std::string ToString(bool whole, Error& err); 228 | std::string Inspect(Error& err); 229 | }; 230 | 231 | class JSObject : public HeapObject { 232 | public: 233 | V8_VALUE_DEFAULT_METHODS(JSObject, HeapObject); 234 | 235 | inline HeapObject Properties(Error& err); 236 | inline HeapObject Elements(Error& err); 237 | 238 | std::string Inspect(InspectOptions* options, Error& err); 239 | std::string InspectInternalFields(Error& err); 240 | std::string InspectProperties(Error& err); 241 | 242 | std::string InspectElements(Error& err); 243 | std::string InspectDictionary(Error& err); 244 | std::string InspectDescriptors(Map map, Error& err); 245 | void Keys(std::vector& keys, Error& err); 246 | 247 | /** Return all the key/value pairs for properties on a JSObject 248 | * This allows keys to be inflated to JSStrings later once we know if 249 | * they are needed. 250 | */ 251 | std::vector> Entries(Error& err); 252 | 253 | Value GetProperty(std::string key_name, Error& err); 254 | int64_t GetArrayLength(Error& err); 255 | Value GetArrayElement(int64_t pos, Error& err); 256 | 257 | static inline bool IsObjectType(LLV8* v8, int64_t type); 258 | 259 | protected: 260 | template 261 | T GetInObjectValue(int64_t size, int index, Error& err); 262 | void ElementKeys(std::vector& keys, Error& err); 263 | void DictionaryKeys(std::vector& keys, Error& err); 264 | void DescriptorKeys(std::vector& keys, Map map, Error& err); 265 | std::vector> DictionaryEntries(Error& err); 266 | std::vector> DescriptorEntries(Map map, Error& err); 267 | Value GetDictionaryProperty(std::string key_name, Error& err); 268 | Value GetDescriptorProperty(std::string key_name, Map map, Error& err); 269 | }; 270 | 271 | class JSArray : public JSObject { 272 | public: 273 | V8_VALUE_DEFAULT_METHODS(JSArray, JSObject); 274 | 275 | inline Smi Length(Error& err); 276 | 277 | std::string Inspect(InspectOptions* options, Error& err); 278 | }; 279 | 280 | class JSFunction : public JSObject { 281 | public: 282 | V8_VALUE_DEFAULT_METHODS(JSFunction, JSObject) 283 | 284 | inline SharedFunctionInfo Info(Error& err); 285 | inline HeapObject GetContext(Error& err); 286 | inline std::string Name(Error& err); 287 | 288 | std::string GetDebugLine(std::string args, Error& err); 289 | std::string Inspect(InspectOptions* options, Error& err); 290 | std::string GetSource(Error& err); 291 | }; 292 | 293 | class JSRegExp : public JSObject { 294 | public: 295 | V8_VALUE_DEFAULT_METHODS(JSRegExp, JSObject); 296 | 297 | inline String GetSource(Error& err); 298 | 299 | std::string Inspect(InspectOptions* options, Error& err); 300 | }; 301 | 302 | class JSDate : public JSObject { 303 | public: 304 | V8_VALUE_DEFAULT_METHODS(JSDate, JSObject); 305 | 306 | inline Value GetValue(Error& err); 307 | 308 | std::string Inspect(Error& err); 309 | }; 310 | 311 | class FixedArrayBase : public HeapObject { 312 | public: 313 | V8_VALUE_DEFAULT_METHODS(FixedArrayBase, HeapObject); 314 | 315 | inline Smi Length(Error& err); 316 | }; 317 | 318 | class FixedArray : public FixedArrayBase { 319 | public: 320 | V8_VALUE_DEFAULT_METHODS(FixedArray, FixedArrayBase) 321 | 322 | template 323 | inline T Get(int index, Error& err); 324 | 325 | inline int64_t LeaData() const; 326 | 327 | std::string Inspect(InspectOptions* options, Error& err); 328 | 329 | private: 330 | std::string InspectContents(int length, Error& err); 331 | }; 332 | 333 | class DescriptorArray : public FixedArray { 334 | public: 335 | V8_VALUE_DEFAULT_METHODS(DescriptorArray, FixedArray) 336 | 337 | inline Smi GetDetails(int index, Error& err); 338 | inline Value GetKey(int index, Error& err); 339 | 340 | // NOTE: Only for DATA_CONSTANT 341 | inline Value GetValue(int index, Error& err); 342 | 343 | inline bool IsFieldDetails(Smi details); 344 | inline bool IsConstFieldDetails(Smi details); 345 | inline bool IsDoubleField(Smi details); 346 | inline int64_t FieldIndex(Smi details); 347 | }; 348 | 349 | class NameDictionary : public FixedArray { 350 | public: 351 | V8_VALUE_DEFAULT_METHODS(NameDictionary, FixedArray) 352 | 353 | inline Value GetKey(int index, Error& err); 354 | inline Value GetValue(int index, Error& err); 355 | inline int64_t Length(Error& err); 356 | }; 357 | 358 | class Context : public FixedArray { 359 | public: 360 | V8_VALUE_DEFAULT_METHODS(Context, FixedArray) 361 | 362 | inline JSFunction Closure(Error& err); 363 | inline Value Previous(Error& err); 364 | inline Value ContextSlot(int index, Error& err); 365 | 366 | std::string Inspect(Error& err); 367 | }; 368 | 369 | class ScopeInfo : public FixedArray { 370 | public: 371 | V8_VALUE_DEFAULT_METHODS(ScopeInfo, FixedArray) 372 | 373 | inline Smi ParameterCount(Error& err); 374 | inline Smi StackLocalCount(Error& err); 375 | inline Smi ContextLocalCount(Error& err); 376 | inline Smi ContextGlobalCount(Error& err); 377 | 378 | inline String ContextLocalName(int index, int param_count, int stack_count, 379 | Error& err); 380 | }; 381 | 382 | class Oddball : public HeapObject { 383 | public: 384 | V8_VALUE_DEFAULT_METHODS(Oddball, HeapObject) 385 | 386 | inline Smi Kind(Error& err); 387 | inline bool IsHoleOrUndefined(Error& err); 388 | inline bool IsHole(Error& err); 389 | 390 | std::string Inspect(Error& err); 391 | }; 392 | 393 | class JSArrayBuffer : public HeapObject { 394 | public: 395 | V8_VALUE_DEFAULT_METHODS(JSArrayBuffer, HeapObject) 396 | 397 | inline int64_t BackingStore(Error& err); 398 | inline int64_t BitField(Error& err); 399 | inline Smi ByteLength(Error& err); 400 | 401 | inline bool WasNeutered(Error& err); 402 | 403 | std::string Inspect(Error& err); 404 | }; 405 | 406 | class JSArrayBufferView : public HeapObject { 407 | public: 408 | V8_VALUE_DEFAULT_METHODS(JSArrayBufferView, HeapObject) 409 | 410 | inline JSArrayBuffer Buffer(Error& err); 411 | inline Smi ByteOffset(Error& err); 412 | inline Smi ByteLength(Error& err); 413 | 414 | std::string Inspect(Error& err); 415 | }; 416 | 417 | class JSFrame : public Value { 418 | public: 419 | V8_VALUE_DEFAULT_METHODS(JSFrame, Value) 420 | 421 | inline int64_t LeaParamSlot(int slot, int count) const; 422 | inline JSFunction GetFunction(Error& err); 423 | inline Value GetReceiver(int count, Error& err); 424 | inline Value GetParam(int slot, int count, Error& err); 425 | 426 | uint32_t GetSourceForDisplay(bool set_line, uint32_t line_start, 427 | uint32_t line_limit, std::string lines[], 428 | uint32_t& lines_found, Error& err); 429 | std::string Inspect(bool with_args, Error& err); 430 | std::string InspectArgs(JSFunction fn, Error& err); 431 | }; 432 | 433 | class LLV8 { 434 | public: 435 | LLV8() : target_(lldb::SBTarget()) {} 436 | 437 | void Load(lldb::SBTarget target); 438 | 439 | private: 440 | template 441 | inline T LoadValue(int64_t addr, Error& err); 442 | 443 | int64_t LoadConstant(const char* name); 444 | int64_t LoadPtr(int64_t addr, Error& err); 445 | double LoadDouble(int64_t addr, Error& err); 446 | std::string LoadString(int64_t addr, int64_t length, Error& err); 447 | std::string LoadTwoByteString(int64_t addr, int64_t length, Error& err); 448 | uint8_t* LoadChunk(int64_t addr, int64_t length, Error& err); 449 | 450 | lldb::SBTarget target_; 451 | lldb::SBProcess process_; 452 | 453 | constants::Common common; 454 | constants::Smi smi; 455 | constants::HeapObject heap_obj; 456 | constants::Map map; 457 | constants::JSObject js_object; 458 | constants::HeapNumber heap_number; 459 | constants::JSArray js_array; 460 | constants::JSFunction js_function; 461 | constants::SharedInfo shared_info; 462 | constants::Code code; 463 | constants::ScopeInfo scope_info; 464 | constants::Context context; 465 | constants::Script script; 466 | constants::String string; 467 | constants::OneByteString one_byte_string; 468 | constants::TwoByteString two_byte_string; 469 | constants::ConsString cons_string; 470 | constants::SlicedString sliced_string; 471 | constants::FixedArrayBase fixed_array_base; 472 | constants::FixedArray fixed_array; 473 | constants::Oddball oddball; 474 | constants::JSArrayBuffer js_array_buffer; 475 | constants::JSArrayBufferView js_array_buffer_view; 476 | constants::JSRegExp js_regexp; 477 | constants::JSDate js_date; 478 | constants::DescriptorArray descriptor_array; 479 | constants::NameDictionary name_dictionary; 480 | constants::Frame frame; 481 | constants::Types types; 482 | 483 | friend class Value; 484 | friend class JSFrame; 485 | friend class Smi; 486 | friend class HeapObject; 487 | friend class Map; 488 | friend class String; 489 | friend class Script; 490 | friend class SharedFunctionInfo; 491 | friend class Code; 492 | friend class JSFunction; 493 | friend class OneByteString; 494 | friend class TwoByteString; 495 | friend class ConsString; 496 | friend class SlicedString; 497 | friend class HeapNumber; 498 | friend class JSObject; 499 | friend class JSArray; 500 | friend class FixedArrayBase; 501 | friend class FixedArray; 502 | friend class DescriptorArray; 503 | friend class NameDictionary; 504 | friend class Context; 505 | friend class ScopeInfo; 506 | friend class Oddball; 507 | friend class JSArrayBuffer; 508 | friend class JSArrayBufferView; 509 | friend class JSRegExp; 510 | friend class JSDate; 511 | friend class CodeMap; 512 | friend class llnode::FindJSObjectsVisitor; 513 | friend class llnode::FindReferencesCmd; 514 | }; 515 | 516 | #undef V8_VALUE_DEFAULT_METHODS 517 | 518 | } // namespace v8 519 | } // namespace llnode 520 | 521 | #endif // SRC_LLV8_H_ 522 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const os = require('os'); 5 | const path = require('path'); 6 | const util = require('util'); 7 | const spawn = require('child_process').spawn; 8 | const EventEmitter = require('events').EventEmitter; 9 | 10 | exports.fixturesDir = path.join(__dirname, 'fixtures'); 11 | exports.buildDir = path.join(__dirname, '..', 'out', 'Release'); 12 | 13 | exports.core = path.join(os.tmpdir(), 'core'); 14 | exports.ranges = exports.core + '.ranges'; 15 | 16 | let pluginName; 17 | if (process.platform === 'darwin') 18 | pluginName = 'llnode.dylib'; 19 | else if (process.platform === 'windows') 20 | pluginName = 'llnode.dll'; 21 | else 22 | pluginName = path.join('lib.target', 'llnode.so'); 23 | 24 | exports.llnodePath = path.join(exports.buildDir, pluginName); 25 | 26 | function Session(scenario) { 27 | EventEmitter.call(this); 28 | 29 | // lldb -- node scenario.js 30 | this.lldb = spawn(process.env.TEST_LLDB_BINARY || 'lldb', [ 31 | '--', 32 | process.execPath, 33 | '--abort_on_uncaught_exception', 34 | path.join(exports.fixturesDir, scenario) 35 | ], { 36 | stdio: [ 'pipe', 'pipe', 'inherit' ], 37 | env: util._extend(util._extend({}, process.env), { 38 | LLNODE_RANGESFILE: exports.ranges 39 | }) 40 | }); 41 | 42 | this.lldb.stdin.write(`plugin load "${exports.llnodePath}"\n`); 43 | this.lldb.stdin.write('run\n'); 44 | 45 | this.initialized = false; 46 | this.waiting = false; 47 | this.waitQueue = []; 48 | 49 | let buf = ''; 50 | this.lldb.stdout.on('data', (data) => { 51 | buf += data; 52 | 53 | for (;;) { 54 | let index = buf.indexOf('\n'); 55 | if (index === -1) 56 | break; 57 | 58 | const line = buf.slice(0, index); 59 | buf = buf.slice(index + 1); 60 | 61 | if (/process \d+ exited/i.test(line)) 62 | this.kill(); 63 | else if (this.initialized) 64 | this.emit('line', line); 65 | else if (/process \d+ launched/i.test(line)) 66 | this.initialized = true; 67 | } 68 | }); 69 | 70 | // Ignore errors 71 | this.lldb.stdin.on('error', () => {}); 72 | this.lldb.stdout.on('error', () => {}); 73 | } 74 | util.inherits(Session, EventEmitter); 75 | exports.Session = Session; 76 | 77 | Session.create = function create(scenario) { 78 | return new Session(scenario); 79 | }; 80 | 81 | Session.prototype.kill = function kill() { 82 | this.lldb.kill(); 83 | this.lldb = null; 84 | }; 85 | 86 | Session.prototype.quit = function quit() { 87 | this.send('kill'); 88 | this.send('quit'); 89 | }; 90 | 91 | Session.prototype.send = function send(line, callback) { 92 | this.lldb.stdin.write(line + '\n', callback); 93 | }; 94 | 95 | Session.prototype._queueWait = function _queueWait(retry) { 96 | if (this.waiting) { 97 | this.waitQueue.push(retry); 98 | return false; 99 | } 100 | 101 | this.waiting = true; 102 | return true; 103 | }; 104 | 105 | Session.prototype._unqueueWait = function _unqueueWait() { 106 | this.waiting = false; 107 | if (this.waitQueue.length > 0) 108 | this.waitQueue.shift()(); 109 | }; 110 | 111 | Session.prototype.wait = function wait(regexp, callback) { 112 | if (!this._queueWait(() => { this.wait(regexp, callback); })) 113 | return; 114 | 115 | const self = this; 116 | this.on('line', function onLine(line) { 117 | if (!regexp.test(line)) 118 | return; 119 | 120 | self.removeListener('line', onLine); 121 | self._unqueueWait(); 122 | 123 | callback(line); 124 | }); 125 | }; 126 | 127 | Session.prototype.waitBreak = function waitBreak(callback) { 128 | this.wait(/Process \d+ stopped/i, callback); 129 | }; 130 | 131 | Session.prototype.linesUntil = function linesUntil(regexp, callback) { 132 | if (!this._queueWait(() => { this.linesUntil(regexp, callback); })) 133 | return; 134 | 135 | const lines = []; 136 | const self = this; 137 | this.on('line', function onLine(line) { 138 | lines.push(line); 139 | 140 | if (!regexp.test(line)) 141 | return; 142 | 143 | self.removeListener('line', onLine); 144 | self._unqueueWait(); 145 | 146 | callback(lines); 147 | }); 148 | }; 149 | 150 | exports.generateRanges = function generateRanges(cb) { 151 | let script; 152 | if (process.platform === 'darwin') 153 | script = path.join(__dirname, '..', 'scripts', 'otool2segments.py'); 154 | else 155 | script = path.join(__dirname, '..', 'scripts', 'readelf2segments.py'); 156 | 157 | const proc = spawn(script, [ exports.core ], { 158 | stdio: [ null, 'pipe', 'inherit' ] 159 | }); 160 | 161 | proc.stdout.pipe(fs.createWriteStream(exports.ranges)); 162 | 163 | proc.on('exit', (status) => { 164 | cb(status === 0 ? null : new Error('Failed to generate ranges')); 165 | }); 166 | }; 167 | -------------------------------------------------------------------------------- /test/fixtures/inspect-scenario.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('../common'); 4 | 5 | const zlib = require('zlib'); 6 | 7 | let outerVar = 'outer variable'; 8 | 9 | exports.holder = {}; 10 | 11 | function closure() { 12 | 13 | function Class() { 14 | this.x = 1; 15 | this.y = 123.456; 16 | 17 | this.hashmap = {}; 18 | } 19 | 20 | Class.prototype.method = function method() { 21 | throw new Error('Uncaught'); 22 | }; 23 | 24 | const c = new Class(); 25 | 26 | c.hashmap['some-key'] = 42; 27 | c.hashmap['other-key'] = 'ohai'; 28 | c.hashmap['cons-string'] = 29 | 'this could be a bit smaller, but v8 wants big str.'; 30 | c.hashmap['cons-string'] += c.hashmap['cons-string']; 31 | c.hashmap[0] = null; 32 | c.hashmap[4] = undefined; 33 | c.hashmap[23] = /regexp/; 34 | c.hashmap[25] = (a,b)=>{a+b}; 35 | 36 | let scopedVar = 'scoped value'; 37 | let scopedAPI = zlib.createDeflate()._handle; 38 | 39 | exports.holder = scopedAPI; 40 | 41 | c.hashmap.scoped = function name() { 42 | return scopedVar + outerVar + scopedAPI; 43 | }; 44 | 45 | c.method(); 46 | } 47 | 48 | closure(); 49 | -------------------------------------------------------------------------------- /test/fixtures/stack-scenario.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('../common'); 4 | 5 | function first() { 6 | second('second args', 1); 7 | } 8 | 9 | function second(arg1, arg2) { 10 | third('third args', 1, { a: 1 }); 11 | } 12 | 13 | function third(arg1, arg2, arg3) { 14 | const c = new Class(); 15 | 16 | c.method('method args', 1.23, null); 17 | } 18 | 19 | function Class() { 20 | } 21 | 22 | Class.prototype.method = function method(arg1, arg2, arg3) { 23 | throw new Error('Uncaught'); 24 | }; 25 | 26 | first(); 27 | -------------------------------------------------------------------------------- /test/inspect-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tape = require('tape'); 4 | 5 | const common = require('./common'); 6 | 7 | tape('v8 inspect', (t) => { 8 | t.timeoutAfter(15000); 9 | 10 | const sess = common.Session.create('inspect-scenario.js'); 11 | 12 | sess.waitBreak(() => { 13 | sess.send('v8 bt'); 14 | }); 15 | 16 | let that = null; 17 | let fn = null; 18 | 19 | sess.wait(/inspect-scenario.js/, (line) => { 20 | let match = line.match(/method\(this=(0x[0-9a-f]+)[^\n]+fn=(0x[0-9a-f]+)/i); 21 | t.ok(match, 'method should have `this`'); 22 | 23 | that = match[1]; 24 | fn = match[2]; 25 | 26 | sess.send(`v8 inspect ${that}`); 27 | }); 28 | 29 | let hashmap = null; 30 | 31 | sess.wait(/Class/, (line) => { 32 | t.notEqual(line.indexOf(that), -1, 'addr of `Class` should match'); 33 | }); 34 | 35 | sess.linesUntil(/}>/, (lines) => { 36 | lines = lines.join('\n'); 37 | t.ok(/x=/.test(lines), '.x smi property'); 38 | t.ok(/y=123.456/.test(lines), '.y heap number property'); 39 | 40 | const match = lines.match(/hashmap=(0x[0-9a-f]+):/i); 41 | t.ok(match, '.hashmap object property'); 42 | 43 | hashmap = match[1]; 44 | 45 | sess.send(`v8 inspect ${hashmap}`); 46 | }); 47 | 48 | let regexp = null; 49 | let cons = null; 50 | let arrowFunc = null; 51 | 52 | sess.wait(/Object/, (line) => { 53 | t.notEqual(line.indexOf(hashmap), -1, 'addr of `Object` should match'); 54 | }); 55 | 56 | sess.linesUntil(/}>/, (lines) => { 57 | lines = lines.join('\n'); 58 | t.ok(/\[0\]=[^\n]*null/.test(lines), '[0] null element'); 59 | t.ok(/\[4\]=[^\n]*undefined/.test(lines), '[4] undefined element'); 60 | 61 | const reMatch = lines.match( 62 | /\[23\]=(0x[0-9a-f]+):<(?:Object: RegExp|JSRegExp source=\/regexp\/)>/); 63 | t.ok(reMatch, '[23] RegExp element'); 64 | regexp = reMatch[1]; 65 | 66 | const arrowMatch = lines.match( 67 | /\[25\]=(0x[0-9a-f]+):<(function: c.hashmap).*>/); 68 | t.ok(arrowMatch, '[25] Arrow Function element'); 69 | arrowFunc = arrowMatch[1]; 70 | 71 | t.ok(/.some-key=/.test(lines), '.some-key property'); 72 | t.ok(/.other-key=[^\n]*/.test(lines), 73 | '.other-key property'); 74 | 75 | const consMatch = lines.match( 76 | /.cons-string=(0x[0-9a-f]+):/); 77 | t.ok(consMatch, '.cons-string ConsString property'); 78 | cons = consMatch[1]; 79 | 80 | sess.send(`v8 inspect ${regexp}`); 81 | sess.send(`v8 inspect -F ${cons}`); 82 | }); 83 | 84 | sess.linesUntil(/}>/, (lines) => { 85 | lines = lines.join('\n'); 86 | t.ok(/source=\/regexp\//.test(lines) || 87 | /\.source=[^\n]*/.test(lines), 88 | 'regexp.source'); 89 | }); 90 | 91 | sess.linesUntil(/">/, (lines) => { 92 | lines = lines.join('\n'); 93 | t.notEqual( 94 | lines.indexOf('this could be a bit smaller, but v8 wants big str.' + 95 | 'this could be a bit smaller, but v8 wants big str.'), 96 | -1, 97 | 'cons string content'); 98 | 99 | sess.send(`v8 inspect -s ${arrowFunc}`); 100 | }); 101 | 102 | sess.linesUntil(/^>/, (lines) => { 103 | lines = lines.join('\n'); 104 | // Include 'source:' and '>' to act as boundaries. (Avoid 105 | // passing if the whole file it displayed instead of just 106 | // the function we want.) 107 | const arrowSource = 'source:\n' + 108 | 'function c.hashmap.(anonymous function)(a,b)=>{a+b}\n' + 109 | '>' 110 | 111 | t.ok(lines.includes( 112 | arrowSource), 113 | 'arrow method source'); 114 | 115 | sess.send(`v8 inspect -s ${fn}`); 116 | }); 117 | 118 | sess.linesUntil(/^>/, (lines) => { 119 | lines = lines.join('\n'); 120 | 121 | // Include 'source:' and '>' to act as boundaries. (Avoid 122 | // passing if the whole file it displayed instead of just 123 | // the function we want.) 124 | const methodSource = " source:\n" + 125 | "function method() {\n" + 126 | " throw new Error('Uncaught');\n" + 127 | " }\n" + 128 | ">" 129 | 130 | t.ok(lines.includes( 131 | methodSource), 132 | 'method source found'); 133 | 134 | if (process.version < 'v5.0.0') { 135 | sess.quit(); 136 | t.end(); 137 | } else { 138 | // No Context debugging for older node.js 139 | t.ok(/\(previous\)/.test(lines), 'method.previous'); 140 | t.ok(/scopedVar[^\n]+"scoped value"/.test(lines), 'method.scopedValue'); 141 | 142 | let match = lines.match( 143 | /scopedAPI=(0x[0-9a-f]+)[^\n]+Zlib/i); 144 | t.ok(match, '`method` should have `scopedAPI`'); 145 | 146 | sess.send(`v8 inspect ${match[1]}`); 147 | 148 | match = lines.match( 149 | /\(closure\)=(0x[0-9a-f]+)[^\n]+function: closure/i); 150 | t.ok(match, '`method` should have `closure`'); 151 | 152 | sess.send(`v8 inspect ${match[1]}`); 153 | } 154 | }); 155 | 156 | sess.linesUntil(/}>/, (lines) => { 157 | lines = lines.join('\n'); 158 | t.ok(/internal fields/.test(lines), 'method.scopedAPI.internalFields'); 159 | }); 160 | 161 | sess.linesUntil(/}>/, (lines) => { 162 | lines = lines.join('\n'); 163 | t.ok(/outerVar[^\n]+"outer variable"/.test(lines), 164 | 'method.closure.outerVar'); 165 | 166 | sess.quit(); 167 | t.end(); 168 | }); 169 | }); 170 | -------------------------------------------------------------------------------- /test/scan-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // No `process save-core` on linuxes :( 4 | if (process.platform !== 'darwin') 5 | return; 6 | 7 | const tape = require('tape'); 8 | 9 | const common = require('./common'); 10 | 11 | tape('v8 findrefs and friends', (t) => { 12 | t.timeoutAfter(90000); 13 | 14 | const sess = common.Session.create('inspect-scenario.js'); 15 | 16 | sess.waitBreak(() => { 17 | sess.send(`process save-core ${common.core}`); 18 | // Just a separator 19 | sess.send('version'); 20 | }); 21 | 22 | sess.wait(/lldb\-/, () => { 23 | t.ok(true, 'Saved core'); 24 | 25 | sess.send(`target create -c ${common.core}`); 26 | }); 27 | 28 | sess.wait(/Core file[^\n]+was loaded/, () => { 29 | t.ok(true, 'Loaded core'); 30 | 31 | common.generateRanges((err) => { 32 | t.error(err, 'generateRanges'); 33 | sess.send('version'); 34 | }); 35 | }); 36 | 37 | sess.wait(/lldb\-/, () => { 38 | t.ok(true, 'Generated ranges'); 39 | 40 | sess.send('v8 findjsobjects'); 41 | // Just a separator 42 | sess.send('version'); 43 | }); 44 | 45 | sess.linesUntil(/lldb\-/, (lines) => { 46 | t.ok(/\d+ Zlib/.test(lines.join('\n')), 'Zlib should be in findjsobjects'); 47 | 48 | sess.send('v8 findjsinstances Zlib'); 49 | // Just a separator 50 | sess.send('version'); 51 | }); 52 | 53 | sess.linesUntil(/lldb\-/, (lines) => { 54 | // Find refs to every Zlib instance 55 | let found = false; 56 | for (let i = lines.length - 1; i >= 0; i--) { 57 | const match = lines[i].match(/(0x[0-9a-f]+):/i); 58 | if (!match) 59 | continue; 60 | 61 | found = true; 62 | sess.send(`v8 findrefs ${match[1]}`); 63 | } 64 | t.ok(found, 'Zlib should be in findjsinstances'); 65 | 66 | // Just a separator 67 | sess.send('version'); 68 | }); 69 | 70 | sess.linesUntil(/lldb\-/, (lines) => { 71 | t.ok(/Deflate\._handle/.test(lines.join('\n')), 'Should find reference'); 72 | t.ok(/Object\.holder/.test(lines.join('\n')), 'Should find reference #2'); 73 | 74 | sess.send('target delete 1'); 75 | sess.quit(); 76 | t.end(); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/stack-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tape = require('tape'); 4 | 5 | const common = require('./common'); 6 | 7 | tape('v8 stack', (t) => { 8 | t.timeoutAfter(15000); 9 | 10 | const sess = common.Session.create('stack-scenario.js'); 11 | sess.waitBreak(() => { 12 | sess.send('v8 bt'); 13 | }); 14 | 15 | sess.wait(/stack-scenario.js/, (line) => { 16 | t.ok(/method\(this=.*Class.*method args.*Number: 1\.23.*null/.test(line), 17 | 'Class method name and args'); 18 | 19 | // TODO(indutny): line numbers are off 20 | t.ok(/stack-scenario.js:22:41/.test(line), 21 | 'Class method file pos'); 22 | }); 23 | 24 | sess.wait(/stack-scenario.js/, (line) => { 25 | t.ok(/third\(.*third args.*Smi: 1.*Object/.test(line), 26 | 'Third function name and args'); 27 | 28 | // TODO(indutny): line numbers are off 29 | t.ok(/stack-scenario.js:13:15/.test(line), 'Third function file pos'); 30 | }); 31 | 32 | sess.wait(/stack-scenario.js/, (line) => { 33 | t.ok(/second\(.*second args.*Smi: 1/.test(line), 34 | 'Second function name and args'); 35 | 36 | // TODO(indutny): line numbers are off 37 | t.ok(/stack-scenario.js:9:16/.test(line), 'Second function file pos'); 38 | }); 39 | 40 | sess.wait(/stack-scenario.js/, (line) => { 41 | t.ok(/first/.test(line), 'first function name'); 42 | 43 | // TODO(indutny): line numbers are off 44 | t.ok(/stack-scenario.js:5:15/.test(line), 'first function file pos'); 45 | 46 | sess.quit(); 47 | t.end(); 48 | }); 49 | }); 50 | --------------------------------------------------------------------------------