├── .gitignore ├── .gitmodules ├── CHANGES.md ├── CONTRIBUTING.md ├── GNUmakefile ├── LICENSE ├── Makefile.arch.defs ├── Makefile.arch.targ ├── Makefile.targ ├── README.md ├── docs ├── development.md └── usage.md ├── package.json ├── src ├── mdb_v8.c ├── mdb_v8_array.c ├── mdb_v8_cfg.c ├── mdb_v8_dbg.h ├── mdb_v8_dbi.c ├── mdb_v8_dbi.h ├── mdb_v8_function.c ├── mdb_v8_impl.h ├── mdb_v8_strbuf.c ├── mdb_v8_string.c ├── mdb_v8_subr.c ├── mdb_v8_version.h ├── mdb_v8_whatis.c ├── v8cfg.h └── v8dbg.h ├── test ├── lib │ ├── buffer-commands.js │ └── runtime-versions.js ├── standalone │ ├── common.js │ ├── gcore_self.js │ ├── tst.arrays.js │ ├── tst.jsclosure.js │ ├── tst.jsfindrefs.js │ ├── tst.postmortem_basic.js │ ├── tst.postmortem_details.js │ ├── tst.postmortem_findjsobjects.js │ ├── tst.postmortem_jsstack.js │ └── tst.v8whatis.js └── test-programs │ ├── defer-abort.js │ ├── explicit-abort.js │ └── gcore-loop.js ├── tools ├── catest ├── cstyle.pl ├── dumpjsobjects ├── jsl.node.conf ├── mdbv8diff │ ├── issues │ │ └── issue-35.js │ ├── mdbv8diff │ ├── package.json │ └── vstream-join.js ├── mk │ ├── Makefile.node_modules.defs │ └── Makefile.node_modules.targ ├── mkversion ├── publish └── runtests_node └── version /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | make_stamps 4 | tools/mdbv8diff/node_modules 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/illumos-libavl"] 2 | path = deps/illumos-libavl 3 | url = https://github.com/joyent/illumos-libavl 4 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | # mdb_v8 changelog 13 | 14 | ## 1.4.4 (2024-11) 15 | 16 | * OS-8594 mdb_v8 update for gcc 14 17 | 18 | ## v1.4.3 (2021-12) 19 | 20 | * OS-8326 Let smartos-live be buildable with gcc10 21 | * #126 fix compilation with GCC10 22 | 23 | ## v1.4.2 (2020-05) 24 | 25 | * #123 fix compilation with GCC9 26 | 27 | ## v1.4.1 (2019-08) 28 | 29 | * OS-7960 need fixes for NULL as a pointer 30 | * #114 error: left shift of negative value 31 | 32 | ## v1.4.0 (2019-04) 33 | 34 | * #107 would like dcmd for heuristically finding back references 35 | * #111 want `::v8whatis` 36 | * #112 stack corruption in jsobj\_properties() 37 | 38 | ## v1.3.0 (2018-02-09) 39 | 40 | * #27 want streaming way to iterate array elements 41 | * #100 jsprint shows more "hole" values than it should 42 | * #101 test suite could use common mechanism for standalone tests 43 | * #102 mdb\_v8 misprinted object in 32-bit Node v4 core file 44 | * #104 test suite should take more care to avoid GC during gcore 45 | 46 | ## v1.2.1 (2017-09-20) 47 | 48 | * #96 jsclosure crash on invalid context index 49 | 50 | ## v1.2.0 (2017-08-31) 51 | 52 | * #44 want "jsfunction" dcmd 53 | * #88 want better support for bound functions 54 | * #91 CTRL+C of ::findjsobjects, followed by ::findjsobjects reports only some objects 55 | * #85 missing dependency for mdb\_v8\_version.c leads to build failure 56 | 57 | ## v1.1.4 (2017-01-30) 58 | 59 | * #77 Node v6.6.0 issues 60 | * #72: tests cannot run on non-release versions of node 61 | 62 | ## v1.1.3 (2016-06-03) 63 | 64 | * #66 mdb_v8 does not support core files generated by processes that use V8 65 | bundled with node v6.0.0 66 | * #68 Crash on ::findjsobjects -r with Node v4.3.1 (V8 4.5.103.35) 67 | * #61 cannot print floating-point values for dumps from 0.12 68 | * #58: Trying to examine a Buffer in a dump from node 0.12.9 69 | * #52 mdb dumped core after using jsclosure on invalid closure 70 | * #51 v8internal fails for valid JSObject 71 | 72 | ## v1.1.2 (2015-11-12) 73 | 74 | * #48 want tool for running tests on multiple Node versions 75 | * #35 clean up interfaces for working with functions and strings 76 | 77 | The change for #35 fixed a number of small issues in printing objects and 78 | strings. 79 | 80 | ## v1.1.1 (2015-10-02) 81 | 82 | * #40: fix JSArrayBufferView's buffer offset missing 83 | * #28 don't allow release builds that won't work on older platforms 84 | 85 | ## v1.1.0 (2015-10-02) 86 | 87 | This version fixes support for Node v4. 88 | 89 | * #36 Update mdb\_v8 for V8 4.6.x 90 | * #32 fix ::findjsobjects for V8 4.5.x 91 | 92 | ## v1.0.0 (2015-09-09) 93 | 94 | * #25 fix ia32 build 95 | * #23 support V8 4.4.x and 4.5.x 96 | * #20 want ::jsfunctions -l 97 | 98 | ## v0.11.1 (2015-08-20) 99 | 100 | * #18 ::jsfunctions help text does not agree with implementation 101 | 102 | ## v0.11.0 (2015-08-20) 103 | 104 | * #17 add information about closures and their variables 105 | 106 | ## v0.10.0 (2015-07-17) 107 | 108 | * #10 some JSDate objects are not printed 109 | * #9 add basic support for printing RegExps 110 | * #8 dmod crashes on some very bad strings 111 | * #7 some objects' properties are not printed correctly on 0.12 112 | * #5 update license to MPL 2.0 113 | * #6 indjsobjects could prune more garbage 114 | * #4 document debug metadata and design constraints 115 | * #3 JavaScript tests should be style-clean and lint-clean 116 | * #2 import Node.js postmortem tests 117 | 118 | ## v0.9.0 (2015-07-16) 119 | 120 | * first release from this repository, roughly equivalent to 121 | the version shipped in Node v0.12 and SmartOS 20150709. 122 | 123 | 124 | ## Early history of mdb_v8 125 | 126 | The canonical source for mdb\_v8 has moved between several repositories. To 127 | avoid confusion between different versions, here's the history: 128 | 129 | * 2011-12-16: mdb\_v8 is initially [integrated](https://github.com/joyent/illumos-joyent/commit/48f2bcac10415ae79c34b6e8d8870a135b57a6c9) 130 | into the [SmartOS](https://smartos.org/) distribution of the 131 | [illumos](https://www.illumos.org/) operating system. 132 | * 2013-07-17: mdb\_v8 is copied into the Node repository so that the dmod can 133 | be built into Node binary. At this point, the canonical copy lives in the 134 | Node repo, but SmartOS continues to deliver its own copy. The #master version 135 | of both copies is identical except for the license, and subsequent changes are 136 | applied to both versions. 137 | * 2015-07-14: mdb\_v8 is copied into this repository to allow for its own 138 | documentation and test suite and to streamline development. The copies in 139 | both Node and SmartOS are considered downstream. Changes should be made to 140 | this copy first, then propagated into these copies. 141 | 142 | The initial commit in this repository represents the copy of mdb\_v8 in illumos 143 | at the time of the commit, which is equivalent to the copy of mdb\_v8 in Node 144 | v0.12.7 except for the license and cstyle-related changes. The first release 145 | from this repository was v0.9.0, which is functionally (nearly) equivalent to 146 | the versions in Node v0.12.7 and SmartOS 2015-07-09. 147 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | See the [Developer's Notes](docs/development.md). 12 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2018, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile for the V8 MDB dmod. This build produces 32-bit and 64-bit binaries 13 | # for mdb_v8 in the "build" directory. 14 | # 15 | # Targets: 16 | # 17 | # all builds the mdb_v8.so shared objects 18 | # 19 | # check run style checker on source files 20 | # 21 | # clean removes all generated files 22 | # 23 | # This Makefile has been designed to work out of the box (without manual or 24 | # automatic configuration) on illumos systems. Other systems do not support 25 | # MDB, so the build will not work there. In principle, you can change most 26 | # of the items under the CONFIGURATION sections, but you should not need to. 27 | # 28 | 29 | # 30 | # CONFIGURATION FOR BUILDERS 31 | # 32 | 33 | # Directory for output objects 34 | MDBV8_BUILD = build 35 | # Output object name 36 | MDBV8_SONAME = mdb_v8.so 37 | # Tag binaries with "dev" or "release". This should be a valid C string. 38 | MDBV8_VERS_TAG = "dev" 39 | 40 | 41 | # 42 | # CONFIGURATION FOR DEVELOPERS 43 | # 44 | 45 | # 46 | # List of source files that will become objects. (These entries do not include 47 | # the "src/" directory prefix.) 48 | # 49 | MDBV8_SOURCES = \ 50 | mdb_v8.c \ 51 | mdb_v8_array.c \ 52 | mdb_v8_cfg.c \ 53 | mdb_v8_dbi.c \ 54 | mdb_v8_function.c \ 55 | mdb_v8_strbuf.c \ 56 | mdb_v8_string.c \ 57 | mdb_v8_subr.c \ 58 | mdb_v8_whatis.c 59 | 60 | MDBV8_GENSOURCES = mdb_v8_version.c 61 | 62 | # List of source files to run through cstyle. This includes header files. 63 | MDBV8_CSTYLE_SOURCES = $(wildcard src/*.c src/*.h) 64 | 65 | # Compiler flags 66 | CFLAGS += -Werror -Wall -Wextra -fPIC -fno-omit-frame-pointer 67 | # Including DWARF information allows us to debug the dmod using MDB's support 68 | # for runtime conversion of DWARF to CTF. 69 | CFLAGS += -g -O 70 | 71 | CPPFLAGS += -DMDBV8_VERS_TAG='$(MDBV8_VERS_TAG)' 72 | # XXX These should be removed. 73 | CFLAGS += -Wno-unused-parameter \ 74 | -Wno-missing-field-initializers \ 75 | -Wno-sign-compare 76 | # This is necessary for including avl_impl.h. 77 | CFLAGS += -Wno-unknown-pragmas 78 | 79 | # Linker flags (including dependent libraries) 80 | LDFLAGS += -lproc 81 | LDFLAGS.64 += -lproc 82 | SOFLAGS = -Wl,-soname=$(MDBV8_SONAME) 83 | 84 | # Path to cstyle.pl tool 85 | CSTYLE = tools/cstyle.pl 86 | CSTYLE_FLAGS += -cCp 87 | 88 | # Path to catest tool 89 | CATEST = tools/catest 90 | 91 | # JavaScript source files (used in test code) 92 | JS_FILES = $(wildcard test/standalone/*.js) \ 93 | $(wildcard tools/mdbv8diff/*.js) \ 94 | $(wildcard tools/mdbv8diff/issues/*.js) \ 95 | tools/mdbv8diff/mdbv8diff 96 | JSL_FILES_NODE = $(JS_FILES) 97 | JSSTYLE_FILES = $(JS_FILES) 98 | JSL_CONF_NODE = tools/jsl.node.conf 99 | 100 | 101 | # 102 | # INTERNAL DEFINITIONS 103 | # 104 | MDBV8_ARCH = ia32 105 | include Makefile.arch.defs 106 | MDBV8_ARCH = amd64 107 | include Makefile.arch.defs 108 | 109 | LIBAVL_SUBMODULE = deps/illumos-libavl 110 | CPPFLAGS += -I$(LIBAVL_SUBMODULE)/include 111 | 112 | $(LIBAVL_amd64): CFLAGS_ARCH += -m64 113 | $(MDBV8_TARGETS_amd64): CFLAGS += -m64 114 | $(MDBV8_TARGETS_amd64): SOFLAGS += -m64 115 | 116 | $(LIBAVL_ia32): CFLAGS_ARCH += -m32 117 | $(MDBV8_TARGETS_ia32): CFLAGS += -m32 118 | $(MDBV8_TARGETS_ia32): SOFLAGS += -m32 119 | 120 | # 121 | # DEFINITIONS USED AS RECIPES 122 | # 123 | MKDIRP = mkdir -p $@ 124 | COMPILE.c = $(CC) -o $@ -c $(CFLAGS) $(CPPFLAGS) $^ 125 | MAKESO_ia32 = $(CC) -o $@ -shared $(SOFLAGS) $(LDFLAGS) $^ 126 | MAKESO_amd64 = $(CC) -o $@ -shared $(SOFLAGS) $(LDFLAGS.64) $^ 127 | GITDESCRIBE = $(shell git describe --all --long --dirty | \ 128 | awk -F'-g' '{print $$NF}') 129 | 130 | # 131 | # Include eng.git Makefile for managing "node_modules" directory. A number of 132 | # items that are normally provided by Makefile.defs are specified here directly. 133 | # 134 | TOP = $(shell pwd) 135 | NPM = npm 136 | MAKE_STAMPS_DIR = make_stamps 137 | CLEAN_FILES += $(MAKE_STAMPS_DIR) 138 | MAKE_STAMP_REMOVE = mkdir -p $(@D); rm -f $(@) 139 | MAKE_STAMP_CREATE = mkdir -p $(@D); touch $(@) 140 | include ./tools/mk/Makefile.node_modules.defs 141 | 142 | # 143 | # TARGETS 144 | # 145 | .PHONY: all 146 | all: $(MDBV8_ALLTARGETS) 147 | 148 | check: check-cstyle 149 | 150 | .PHONY: check-cstyle 151 | check-cstyle: 152 | $(CSTYLE) $(CSTYLE_FLAGS) $(MDBV8_CSTYLE_SOURCES) 153 | 154 | CLEAN_FILES += $(MDBV8_BUILD) 155 | 156 | .PHONY: test 157 | test: $(MDBV8_ALLTARGETS) $(STAMP_NODE_MODULES) 158 | $(CATEST) -a 159 | 160 | .PHONY: prepush 161 | prepush: check test 162 | 163 | .PHONY: release 164 | release: MDBV8_VERS_TAG := "release, from $(GITDESCRIBE)" 165 | release: clean $(MDBV8_ALLTARGETS) 166 | 167 | # 168 | # Makefile.arch.targ is parametrized by MDBV8_ARCH. It defines a group of 169 | # the current value of MDBV8_ARCH. When we include it the first time, it 170 | # defines targets for the 32-bit object files and shared object file. The 171 | # second time, it defines targets for the 64-bit object files and shared object 172 | # file. This avoids duplicating Makefile snippets, though admittedly today 173 | # these snippets are short enough that it hardly makes much difference. 174 | # 175 | MDBV8_ARCH=ia32 176 | include Makefile.arch.targ 177 | MDBV8_ARCH=amd64 178 | include Makefile.arch.targ 179 | 180 | # 181 | # mdb_v8_version.c is generated based on the "version" file. The version number 182 | # is baked into the binary itself (so the dmod can report its version) and also 183 | # used in the publish target. 184 | # 185 | $(MDBV8_BUILD)/mdb_v8_version.c: version | $(MDBV8_BUILD) 186 | tools/mkversion < $^ > $@ 187 | 188 | $(MDBV8_BUILD): 189 | $(MKDIRP) 190 | 191 | # 192 | # Include common Joyent Makefile for JavaScript "check" targets and 193 | # "node_modules" management targets. 194 | # 195 | include ./Makefile.targ 196 | include ./tools/mk/Makefile.node_modules.targ 197 | -------------------------------------------------------------------------------- /Makefile.arch.defs: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2015, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile.arch.defs: this Makefile contains definitions that will be sourced 13 | # twice: once for each architecture (ia32 and amd64). This avoids duplicating a 14 | # bunch of definitions that are otherwise identical. 15 | # 16 | # These definitions must be immediate rather than lazy (":=" rather than "=") 17 | # because values on the right hand side (notably MDBV8_ARCH) may change after 18 | # this file is included, and we want to bind to their current values. 19 | # 20 | 21 | # Architecture-specific build directory 22 | MDBV8_BUILD_$(MDBV8_ARCH) := $(MDBV8_BUILD)/$(MDBV8_ARCH) 23 | # Path to architecture-specific shared object file 24 | MDBV8_DYLIB_$(MDBV8_ARCH) := $(MDBV8_BUILD_$(MDBV8_ARCH))/$(MDBV8_SONAME) 25 | # List of full paths to architecture-specific object files 26 | MDBV8_OBJECTS_$(MDBV8_ARCH) := \ 27 | $(MDBV8_SOURCES:%.c=$(MDBV8_BUILD_$(MDBV8_ARCH))/%.o) \ 28 | $(MDBV8_GENSOURCES:%.c=$(MDBV8_BUILD_$(MDBV8_ARCH))/%.o) 29 | 30 | # List of architecture-specific targets (for defining target-specific variables) 31 | MDBV8_TARGETS_$(MDBV8_ARCH) := \ 32 | $(MDBV8_DYLIB_$(MDBV8_ARCH)) MDBV8_OBJECTS_$(MDBV8_ARCH) 33 | # Append this shared object to the list of "all" targets 34 | MDBV8_ALLTARGETS := \ 35 | $(MDBV8_ALLTARGETS) $(MDBV8_DYLIB_$(MDBV8_ARCH)) 36 | 37 | # Private copy of libavl 38 | LIBAVL_$(MDBV8_ARCH) := $(MDBV8_BUILD_$(MDBV8_ARCH))/libavl.a 39 | EXTRA_STATIC_LIBS_$(MDBV8_ARCH) += $(LIBAVL_$(MDBV8_ARCH)) 40 | -------------------------------------------------------------------------------- /Makefile.arch.targ: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2015, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile.arch.targ: this Makefile contains targets that will be sourced twice: 13 | # once for each architecture (ia32 and amd64). This avoids duplicating a bunch 14 | # of recipes that are otherwise identical. 15 | # 16 | # We're able to use the exact same recipe for both 32-bit and 64-bit targets 17 | # because the recipe makes use of target-specific variables that will change the 18 | # command-line as needed. Specifically, CFLAGS and SOFLAGS will change based on 19 | # the architecture, so as long as we use those variables (or other variables 20 | # that use them), we don't need to do anything differently for the two 21 | # architectures. 22 | # 23 | 24 | $(MDBV8_BUILD_$(MDBV8_ARCH))/%.o: src/%.c | $(MDBV8_BUILD_$(MDBV8_ARCH)) 25 | $(COMPILE.c) 26 | 27 | $(MDBV8_BUILD_$(MDBV8_ARCH))/%.o: $(MDBV8_BUILD)/%.c | $(MDBV8_BUILD_$(MDBV8_ARCH)) 28 | $(COMPILE.c) 29 | 30 | $(MDBV8_DYLIB_$(MDBV8_ARCH)): $(MDBV8_OBJECTS_$(MDBV8_ARCH)) $(EXTRA_STATIC_LIBS_$(MDBV8_ARCH)) | $(LIBAVL_$(MDBV8_ARCH)) 31 | $(MAKESO_$(MDBV8_ARCH)) 32 | 33 | $(MDBV8_BUILD_$(MDBV8_ARCH)): 34 | $(MKDIRP) 35 | 36 | # 37 | # Build our private, static libavl by descending into the submodule and running 38 | # its build system. This target depends on the ".git" directory in the 39 | # submodule to trigger another recipe to checkout the submodule as needed. 40 | # 41 | $(LIBAVL_$(MDBV8_ARCH)): $(LIBAVL_SUBMODULE)/.git 42 | BUILD_DIR=$$(pwd)/$(shell dirname $@) \ 43 | CFLAGS="-fPIC $(CFLAGS_ARCH)" \ 44 | $(MAKE) -C $(LIBAVL_SUBMODULE) 45 | -------------------------------------------------------------------------------- /Makefile.targ: -------------------------------------------------------------------------------- 1 | # -*- mode: makefile -*- 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # Makefile.targ: common targets. 14 | # 15 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 16 | # into other repos as-is without requiring any modifications. If you find 17 | # yourself changing this file, you should instead update the original copy in 18 | # eng.git and then update your repo to use the new version. 19 | # 20 | # This Makefile defines several useful targets and rules. You can use it by 21 | # including it from a Makefile that specifies some of the variables below. 22 | # 23 | # Targets defined in this Makefile: 24 | # 25 | # check Checks JavaScript files for lint and style 26 | # Checks bash scripts for syntax 27 | # Checks SMF manifests for validity against the SMF DTD 28 | # 29 | # clean Removes built files 30 | # 31 | # docs Builds restdown documentation in docs/ 32 | # 33 | # prepush Depends on "check" and "test" 34 | # 35 | # test Does nothing (you should override this) 36 | # 37 | # xref Generates cscope (source cross-reference index) 38 | # 39 | # For details on what these targets are supposed to do, see the Joyent 40 | # Engineering Guide. 41 | # 42 | # To make use of these targets, you'll need to set some of these variables. Any 43 | # variables left unset will simply not be used. 44 | # 45 | # BASH_FILES Bash scripts to check for syntax 46 | # (paths relative to top-level Makefile) 47 | # 48 | # CLEAN_FILES Files to remove as part of the "clean" target. Note 49 | # that files generated by targets in this Makefile are 50 | # automatically included in CLEAN_FILES. These include 51 | # restdown-generated HTML and JSON files. 52 | # 53 | # DOC_FILES Restdown (documentation source) files. These are 54 | # assumed to be contained in "docs/", and must NOT 55 | # contain the "docs/" prefix. 56 | # 57 | # JSL_CONF_NODE Specify JavaScriptLint configuration files 58 | # JSL_CONF_WEB (paths relative to top-level Makefile) 59 | # 60 | # Node.js and Web configuration files are separate 61 | # because you'll usually want different global variable 62 | # configurations. If no file is specified, none is given 63 | # to jsl, which causes it to use a default configuration, 64 | # which probably isn't what you want. 65 | # 66 | # JSL_FILES_NODE JavaScript files to check with Node config file. 67 | # JSL_FILES_WEB JavaScript files to check with Web config file. 68 | # 69 | # JSON_FILES JSON files to be validated 70 | # 71 | # JSSTYLE_FILES JavaScript files to be style-checked 72 | # 73 | # You can also override these variables: 74 | # 75 | # BASH Path to bash (default: "bash") 76 | # 77 | # CSCOPE_DIRS Directories to search for source files for the cscope 78 | # index. (default: ".") 79 | # 80 | # JSL Path to JavaScriptLint (default: "jsl") 81 | # 82 | # JSL_FLAGS_NODE Additional flags to pass through to JSL 83 | # JSL_FLAGS_WEB 84 | # JSL_FLAGS 85 | # 86 | # JSON Path to json tool (default: "json") 87 | # 88 | # JSSTYLE Path to jsstyle (default: "jsstyle") 89 | # 90 | # JSSTYLE_FLAGS Additional flags to pass through to jsstyle 91 | # 92 | # RESTDOWN_EXT By default '.restdown' is required for DOC_FILES 93 | # (see above). If you want to use, say, '.md' instead, then 94 | # set 'RESTDOWN_EXT=.md' in your Makefile. 95 | # 96 | 97 | # 98 | # Defaults for the various tools we use. 99 | # 100 | BASH ?= bash 101 | BASHSTYLE ?= tools/bashstyle 102 | CP ?= cp 103 | CSCOPE ?= cscope 104 | CSCOPE_DIRS ?= . 105 | JSL ?= jsl 106 | JSON ?= json 107 | JSSTYLE ?= jsstyle 108 | MKDIR ?= mkdir -p 109 | MV ?= mv 110 | RESTDOWN_FLAGS ?= 111 | RESTDOWN_EXT ?= .restdown 112 | RMTREE ?= rm -rf 113 | JSL_FLAGS ?= --nologo --nosummary 114 | 115 | ifeq ($(shell uname -s),SunOS) 116 | TAR ?= gtar 117 | else 118 | TAR ?= tar 119 | endif 120 | 121 | 122 | # 123 | # Defaults for other fixed values. 124 | # 125 | BUILD = build 126 | DISTCLEAN_FILES += $(BUILD) 127 | DOC_BUILD = $(BUILD)/docs/public 128 | 129 | # 130 | # Configure JSL_FLAGS_{NODE,WEB} based on JSL_CONF_{NODE,WEB}. 131 | # 132 | ifneq ($(origin JSL_CONF_NODE), undefined) 133 | JSL_FLAGS_NODE += --conf=$(JSL_CONF_NODE) 134 | endif 135 | 136 | ifneq ($(origin JSL_CONF_WEB), undefined) 137 | JSL_FLAGS_WEB += --conf=$(JSL_CONF_WEB) 138 | endif 139 | 140 | # 141 | # Targets. For descriptions on what these are supposed to do, see the 142 | # Joyent Engineering Guide. 143 | # 144 | 145 | # 146 | # Instruct make to keep around temporary files. We have rules below that 147 | # automatically update git submodules as needed, but they employ a deps/*/.git 148 | # temporary file. Without this directive, make tries to remove these .git 149 | # directories after the build has completed. 150 | # 151 | .SECONDARY: $($(wildcard deps/*):%=%/.git) 152 | 153 | # 154 | # This rule enables other rules that use files from a git submodule to have 155 | # those files depend on deps/module/.git and have "make" automatically check 156 | # out the submodule as needed. 157 | # 158 | deps/%/.git: 159 | git submodule update --init deps/$* 160 | 161 | # 162 | # These recipes make heavy use of dynamically-created phony targets. The parent 163 | # Makefile defines a list of input files like BASH_FILES. We then say that each 164 | # of these files depends on a fake target called filename.bashchk, and then we 165 | # define a pattern rule for those targets that runs bash in check-syntax-only 166 | # mode. This mechanism has the nice properties that if you specify zero files, 167 | # the rule becomes a noop (unlike a single rule to check all bash files, which 168 | # would invoke bash with zero files), and you can check individual files from 169 | # the command line with "make filename.bashchk". 170 | # 171 | .PHONY: check-bash 172 | check-bash: $(BASH_FILES:%=%.bashchk) $(BASH_FILES:%=%.bashstyle) 173 | 174 | %.bashchk: % 175 | $(BASH) -n $^ 176 | 177 | %.bashstyle: % 178 | $(BASHSTYLE) $^ 179 | 180 | .PHONY: check-json 181 | check-json: $(JSON_FILES:%=%.jsonchk) 182 | 183 | %.jsonchk: % 184 | $(JSON) --validate -f $^ 185 | 186 | # 187 | # The above approach can be slow when there are many files to check because it 188 | # requires that "make" invoke the check tool once for each file, rather than 189 | # passing in several files at once. For the JavaScript check targets, we define 190 | # a variable for the target itself *only if* the list of input files is 191 | # non-empty. This avoids invoking the tool if there are no files to check. 192 | # 193 | JSL_NODE_TARGET = $(if $(JSL_FILES_NODE), check-jsl-node) 194 | .PHONY: check-jsl-node 195 | check-jsl-node: $(JSL_EXEC) 196 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_NODE) $(JSL_FILES_NODE) 197 | 198 | JSL_WEB_TARGET = $(if $(JSL_FILES_WEB), check-jsl-web) 199 | .PHONY: check-jsl-web 200 | check-jsl-web: $(JSL_EXEC) 201 | $(JSL) $(JSL_FLAGS) $(JSL_FLAGS_WEB) $(JSL_FILES_WEB) 202 | 203 | .PHONY: check-jsl 204 | check-jsl: $(JSL_NODE_TARGET) $(JSL_WEB_TARGET) 205 | 206 | JSSTYLE_TARGET = $(if $(JSSTYLE_FILES), check-jsstyle) 207 | .PHONY: check-jsstyle 208 | check-jsstyle: $(JSSTYLE_EXEC) 209 | $(JSSTYLE) $(JSSTYLE_FLAGS) $(JSSTYLE_FILES) 210 | 211 | .PHONY: check 212 | check: check-jsl check-json $(JSSTYLE_TARGET) check-bash 213 | @echo check ok 214 | 215 | .PHONY: clean 216 | clean:: 217 | -$(RMTREE) $(CLEAN_FILES) 218 | 219 | .PHONY: distclean 220 | distclean:: clean 221 | -$(RMTREE) $(DISTCLEAN_FILES) 222 | 223 | CSCOPE_FILES = cscope.in.out cscope.out cscope.po.out 224 | CLEAN_FILES += $(CSCOPE_FILES) 225 | 226 | .PHONY: xref 227 | xref: cscope.files 228 | $(CSCOPE) -bqR 229 | 230 | .PHONY: cscope.files 231 | cscope.files: 232 | find $(CSCOPE_DIRS) -name '*.c' -o -name '*.h' -o -name '*.cc' \ 233 | -o -name '*.js' -o -name '*.s' -o -name '*.cpp' > $@ 234 | 235 | # 236 | # The "docs" target is complicated because we do several things here: 237 | # 238 | # (1) Use restdown to build HTML and JSON files from each of DOC_FILES. 239 | # 240 | # (2) Copy these files into $(DOC_BUILD) (build/docs/public), which 241 | # functions as a complete copy of the documentation that could be 242 | # mirrored or served over HTTP. 243 | # 244 | # (3) Then copy any directories and media from docs/media into 245 | # $(DOC_BUILD)/media. This allows projects to include their own media, 246 | # including files that will override same-named files provided by 247 | # restdown. 248 | # 249 | # Step (3) is the surprisingly complex part: in order to do this, we need to 250 | # identify the subdirectories in docs/media, recreate them in 251 | # $(DOC_BUILD)/media, then do the same with the files. 252 | # 253 | DOC_MEDIA_DIRS := $(shell find docs/media -type d 2>/dev/null | grep -v "^docs/media$$") 254 | DOC_MEDIA_DIRS := $(DOC_MEDIA_DIRS:docs/media/%=%) 255 | DOC_MEDIA_DIRS_BUILD := $(DOC_MEDIA_DIRS:%=$(DOC_BUILD)/media/%) 256 | 257 | DOC_MEDIA_FILES := $(shell find docs/media -type f 2>/dev/null) 258 | DOC_MEDIA_FILES := $(DOC_MEDIA_FILES:docs/media/%=%) 259 | DOC_MEDIA_FILES_BUILD := $(DOC_MEDIA_FILES:%=$(DOC_BUILD)/media/%) 260 | 261 | # 262 | # Like the other targets, "docs" just depends on the final files we want to 263 | # create in $(DOC_BUILD), leveraging other targets and recipes to define how 264 | # to get there. 265 | # 266 | .PHONY: docs 267 | docs: \ 268 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.html) \ 269 | $(DOC_FILES:%$(RESTDOWN_EXT)=$(DOC_BUILD)/%.json) \ 270 | $(DOC_MEDIA_FILES_BUILD) 271 | 272 | # 273 | # We keep the intermediate files so that the next build can see whether the 274 | # files in DOC_BUILD are up to date. 275 | # 276 | .PRECIOUS: \ 277 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 278 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%json) 279 | 280 | # 281 | # We do clean those intermediate files, as well as all of DOC_BUILD. 282 | # 283 | CLEAN_FILES += \ 284 | $(DOC_BUILD) \ 285 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.html) \ 286 | $(DOC_FILES:%$(RESTDOWN_EXT)=docs/%.json) 287 | 288 | # 289 | # Before installing the files, we must make sure the directories exist. The | 290 | # syntax tells make that the dependency need only exist, not be up to date. 291 | # Otherwise, it might try to rebuild spuriously because the directory itself 292 | # appears out of date. 293 | # 294 | $(DOC_MEDIA_FILES_BUILD): | $(DOC_MEDIA_DIRS_BUILD) 295 | 296 | $(DOC_BUILD)/%: docs/% | $(DOC_BUILD) 297 | $(CP) $< $@ 298 | 299 | docs/%.json docs/%.html: docs/%$(RESTDOWN_EXT) | $(DOC_BUILD) $(RESTDOWN_EXEC) 300 | $(RESTDOWN) $(RESTDOWN_FLAGS) -m $(DOC_BUILD) $< 301 | 302 | $(DOC_BUILD): 303 | $(MKDIR) $@ 304 | 305 | $(DOC_MEDIA_DIRS_BUILD): 306 | $(MKDIR) $@ 307 | 308 | # 309 | # The default "test" target does nothing. This should usually be overridden by 310 | # the parent Makefile. It's included here so we can define "prepush" without 311 | # requiring the repo to define "test". 312 | # 313 | .PHONY: test 314 | test: 315 | 316 | .PHONY: prepush 317 | prepush: check test 318 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | # mdb_v8: postmortem debugging for Node.js 12 | 13 | This repository contains the canonical source for mdb\_v8, an 14 | [mdb](http://illumos.org/man/1/mdb) debugger module ("dmod") for debugging both 15 | live processes and core dumps of programs using Google's [V8 JavaScript 16 | engine](https://developers.google.com/v8/), and particularly 17 | [Node.js](https://nodejs.org/). This module fully supports Node versions 5, 4, 18 | 0.12, and 0.10. Basic functionality (stack traces, printing out objects, and 19 | using `findjsobjects`) should also work on Node versions 0.8, 0.6, and 0.4, but 20 | those versions are not regularly tested. 21 | 22 | Downstream versions of mdb\_v8 exist in both Node.js and SmartOS. See 23 | [CHANGES.md](CHANGES.md) for details. 24 | 25 | 26 | ## Using mdb_v8 27 | 28 | **For information about using these tools, see the [usage 29 | guide](docs/usage.md).** 30 | 31 | 32 | ## Building from source 33 | 34 | You can build mdb\_v8 by cloning this repository and running `make`. It will 35 | only build and run on illumos-based systems. See the [usage 36 | guide](docs/usage.md) for details on system support. 37 | 38 | 39 | ## Binary downloads 40 | 41 | Binaries for mdb\_v8 can be found at 42 | https://us-east.manta.joyent.com/Joyent_Dev/public/mdb_v8. If you have the 43 | [Manta command-line tools](https://www.npmjs.com/package/manta) installed, you 44 | can list the latest binaries with: 45 | 46 | $ mfind -t o $(mget -q /Joyent_Dev/public/mdb_v8/latest) 47 | /Joyent_Dev/public/mdb_v8/v1.4.3/mdb_v8_amd64.so 48 | /Joyent_Dev/public/mdb_v8/v1.4.3/mdb_v8_ia32.so 49 | 50 | You can fetch a specific binary like this (in this case, the 32-bit version 51 | 1.4.2 binary): 52 | 53 | $ mget -O /Joyent_Dev/public/mdb_v8/v1.4.3/mdb_v8_ia32.so 54 | 55 | or using curl: 56 | 57 | $ curl -O https://us-east.manta.joyent.com/Joyent_Dev/public/mdb_v8/v1.4.3/mdb_v8_ia32.so 58 | 59 | This one-liner will get you the latest 32-bit binary: 60 | 61 | $ mget -O $(mget -q /Joyent_Dev/public/mdb_v8/latest)/mdb_v8_ia32.so 62 | 63 | 64 | ## Design goals 65 | 66 | An important design constraint on this tool is that it should not rely on 67 | assistance from the JavaScript runtime environment (i.e., V8) to debug Node.js 68 | programs. This is for many reasons: 69 | 70 | * In production, it's extremely valuable to be able to save a core file and then 71 | restart the program (or let it keep running, but undisturbed by a debugger). 72 | This allows you to restore service quickly, but still debug the problem later. 73 | * There are many important failure modes where support from the runtime is not 74 | available, including crashes in the VM itself, crashes in native libraries and 75 | add-ons, and cases where the threads that could provide that support are 76 | stuck, as in a tight loop (or blocked on other threads that are looping). 77 | * By not requiring runtime support, it's possible to [stop the program at very 78 | specific points of execution (using other tools), save a core file, and then 79 | set the program running again with minimal 80 | disruption](https://www.joyent.com/blog/stopping-a-broken-program-in-its-tracks). 81 | With tools like DTrace, you can stop the program at points that the VM can't 82 | know about, like when a thread is taken off-CPU. 83 | * Many issues span both native code and JavaScript code (e.g., native memory 84 | leaks induced by JavaScript calls), where it's useful to have both native and 85 | JavaScript state available. 86 | 87 | In short, there are many kinds of problems that cannot be debugged with a 88 | debugger that relies on the running process to help debug itself. The ACM Queue 89 | article [Postmortem Debugging in Dynamic 90 | Environments](https://queue.acm.org/detail.cfm?id=2039361) outlines the history 91 | and motivation for postmortem debugging and the challenges underlying postmortem 92 | debugging in higher-level languages. 93 | 94 | 95 | ## Implementation notes 96 | 97 | We built this tool on mdb for two reasons: 98 | 99 | * mdb provides a rich plugin interface through which dmods (debugger modules) 100 | can define their own walkers and commands. These commands can function in a 101 | pipeline, sending and receiving output to and from other commands. These 102 | commands aren't just macros -- they're documented, have options similar to 103 | Unix command-line tools, they can build up their own data structures, and so 104 | on. Plugins run in the address space of the debugger, not the program being 105 | debugged. 106 | * mdb abstracts the notion of a _target_, so the same dmod can be used to debug 107 | both live processes and core files. mdb\_v8 uses mdb's built in facilities 108 | for safely listing symbols, reading memory from the core file, emitting 109 | output, and so on, without knowing how to do any of that itself. 110 | 111 | In order to provide postmortem support, mdb\_v8 has to grok a number of internal 112 | implementation details of the V8 VM. Some algorithms, like property iteration, 113 | are (regrettably) duplicated inside mdb\_v8. But many pieces, particularly 114 | related to the structure of V8 internal fields, are dynamically configured based 115 | on the process being debugged. It works like this: 116 | 117 | * As part of the V8 build process, a C++ file is 118 | [generated](https://github.com/v8/v8/blob/master/tools/gen-postmortem-metadata.py) 119 | that defines a number of global `int`s that describe the class hierarchy that 120 | V8 uses to represent Heap objects. The class names, their inheritance 121 | hierarchy, and their field names are described by the names of these 122 | constants, and the values describe offsets of fields inside class instances. 123 | This C++ file is linked into the final V8 binary. 124 | 125 | You can think of the debug metadata as debug information similar to DWARF or 126 | CTF, but it's considerably lighter-weight than DWARF and much easier to 127 | interpret. Besides that, because of the way V8 defines heap classes, 128 | traditional DWARF or CTF would not have been sufficient to encode the 129 | information we needed because many of the relevant classes and nearly all of 130 | the class members are totally synthetic at compile-time and not present at all 131 | in the final V8 binary. 132 | 133 | Because of this approach (rather than, say, attempting to parse the C++ header 134 | files that describe up the V8 heap), the values of these constants are always 135 | correct for the program being debugged, whether it's 32-bit, 64-bit, or has 136 | any compile-time configuration options altered that would affect these 137 | structures. 138 | * When mdb\_v8 starts up, it reads the values of these symbols from the program 139 | being debugged and uses that information to traverse V8 heap structures. 140 | 141 | An ideal solution would avoid duplicating any VM knowledge in the debugger 142 | module. There are two obvious approaches for doing that: 143 | 144 | 1. In addition to encoding heap structure in the binary at build-time, encode 145 | algorithmic pieces as well. This could use a mechanism similar to the 146 | [DTrace ustack 147 | helper](http://dtrace.org/blogs/dap/2013/11/20/understanding-dtrace-ustack-helpers/), 148 | which allows VMs to encode deep internal details in a way that even the 149 | kernel can safely use, even in delicate kernel contexts. To get to this 150 | point would require figuring out all the kinds of information a debugger 151 | might need and figuring out how the VM could encode it in production binaries 152 | (i.e., efficiently) for execution by an arbitrary debugger. 153 | 2. Alternatively, VMs could provide their own standalone postmortem debugging 154 | tools that could reconstituting a program's state from a core file and then 155 | providing a normal debugging interface. Those debuggers wouldn't necessarily 156 | help with issues that span both native and JavaScript code. 157 | 158 | Both of these approach require considerable first-class support from the VM (and 159 | team, who would have to maintain these implementations), which does not seem to 160 | exist for the case of V8 (or any other VM we know of). The existing approach 161 | requires minimal maintenance because much of the metadata is created through 162 | the same mechanisms in the build process that define the classes and fields 163 | themselves. 164 | 165 | 166 | ## Contributing 167 | 168 | See the [Developer's Notes](docs/development.md) for details. 169 | 170 | 171 | ## License 172 | 173 | With the exception of the "cstyle.pl" tool, all components in this repo are 174 | licensed under the MPL 2.0. "cstyle.pl" is licensed under the CDDL. (Various 175 | pieces in this repo were historically released under other open source licenses 176 | as well.) 177 | -------------------------------------------------------------------------------- /docs/development.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | # mdb_v8 Developer's Notes 12 | 13 | ## Contribution guidelines 14 | 15 | Contributions are welcome, but please help us review your changes (and keep code 16 | quality high) by following these guidelines. **If you have any questions, feel 17 | free to ask.** Don't let these guidelines be a barrier to contributing! 18 | 19 | **Code review:** Changes should be as GitHub pull requests. 20 | 21 | **Use of GitHub issues:** There should be at least one GitHub issue filed for 22 | your change (even trivial changes). This issue should explain the change, the 23 | impact on users, how you've tested it, and any other information that reviewers 24 | might need to know about the change. (Do not put this text in the commit 25 | message. See below.) If you're not sure exactly what change you want to make, 26 | feel free to create an issue to discuss it. 27 | 28 | **Commit messages:** The commit message should consist of one line per 29 | associated issue, each line consisting of the issue number and issue synopsis. 30 | See previous commit messages for examples. There should be no other text in the 31 | commit message. Other information related to the change should generally be in 32 | the GitHub issue comments. 33 | 34 | **Completeness:** Changes should include relevant updates to the documentation, 35 | including CHANGES.md. New commands, walkers, and non-private options must have 36 | associated documentation, both in the dmod (so that "::help DCMD" works) and in 37 | the usage guide. 38 | 39 | **Testing**: All changes should be `make prepush` clean, but additional testing 40 | is probably necessary for most changes. See below. 41 | 42 | 43 | ## Testing 44 | 45 | The appropriate level of testing depends on the scope of the change. Any 46 | non-trivial changes should be tested on: 47 | 48 | - core files from each of the latest several major releases of Node 49 | (e.g., Node v4, Node v0.12, and Node v0.10) 50 | - core files from both 32-bit and 64-bit programs 51 | - core files generated by abort(3c) and core files generated by gcore(1M) 52 | 53 | Depending on the change, you may want to test it manually on an existing 54 | collection of representative core files. If you're not sure about test 55 | requirements, please ask. 56 | 57 | 58 | ### Automated testing 59 | 60 | The automated tests are not a comprehensive regression test suite. Think of 61 | them more as a smoke test. You can run them by running: 62 | 63 | - `make test` (which is run by `make prepush`) 64 | - the `catest` tool, which lets you run individual tests separately 65 | - any of the individual tests by hand (they're standalone programs), which is 66 | likely easier for debugging the tests 67 | 68 | All of these approaches use whichever Node is on your PATH to run a Node program 69 | and generate core files of that program. Your local build of mdb\_v8 is used to 70 | inspect it. 71 | 72 | 73 | ### Automated testing across different Node versions and architectures 74 | 75 | For coverage across 32-bit and 64-bit and multiple Node versions, you can use 76 | the `runtests_node` tool: 77 | 78 | 1. First, run `runtests_node setup SOME_DIRECTORY`, which downloads the 79 | supported Node versions into "SOME\_DIRECTORY". 80 | 2. Then, run `runtests_node run SOME_DIRECTORY`, which executes the full test 81 | suite on each of the versions in the directory. 82 | 83 | Because we've made it pretty straightforward, we expect all changes to pass the 84 | test suite on all of the versions and architectures used by `runtests_node`. 85 | 86 | 87 | ### Memory leak testing 88 | 89 | mdb runs with libumem, which means it's easy to check for memory leaks in C 90 | code. The process basically looks like this: 91 | 92 | 1. Open up MDB on a core file and load mdb\_v8. 93 | 2. Exercise as much mdb\_v8 functionality as you can (or whatever functionality 94 | you're trying to test for leaks. 95 | 3. _On platforms before illumos issue 96 | [6338](https://www.illumos.org/issues/6338) was fixed (which is all platforms 97 | before 2015-10-26)_, run: `::unload libumem.so`. This is the easiest way to 98 | work around that issue. 99 | 4. Use `gcore` to save a core file of your MDB process. 100 | 5. Open up that core file in MDB. (Yes, this gets confusing: you're using MDB 101 | to look at a core file of MDB looking at a core file of a Node program.) 102 | 6. Run `findleaks -v` to look for leaks. 103 | 104 | There's a lot more information about using libumem to debug memory leaks 105 | elsewhere on the web. 106 | 107 | 108 | ### Stress testing 109 | 110 | There's a [dumpjsobjects](../tools/dumpjsobjects) tool that takes a core file 111 | and an mdb\_v8 binary and uses `findjsobjects` and `jsprint` to enumerate all of 112 | the objects in a deterministic order and print them out. With a representative 113 | core file, this is a decent coverage test, since it will likely exercise the 114 | paths for interpreting most types of supported objects. At the very least, this 115 | should not crash. 116 | 117 | 118 | ### Comparing output across mdb_v8 changes 119 | 120 | For some kinds of changes, it's worthwhile to spend time comparing output from 121 | before the change with output after the change to make sure you haven't 122 | introduced any new issues. The `mdbv8diff` tool can be used to compare the 123 | `dumpjsobjects` output from two different versions of mdb\_v8. 124 | 125 | Why not just use `diff(1)`? The challenge is that many changes to mdb\_v8 126 | change the output format slightly or fix nitty bugs that affect a large number 127 | of objects. `diff` can easily produce hundreds of thousands of lines of output, 128 | but there may be only a handful of different _kinds_ of changes, and they may 129 | all be easy to programmatically identify (e.g., the wording of a particular 130 | error message changed). For this reason, `mdbv8diff` provides a small "ignore" 131 | module interface, where you define a function that takes two lines of output 132 | that look different and decides whether the difference corresponds exactly to an 133 | expected change. Sometimes, a diff represents something that's hard to 134 | programmatically recognize but you've manually confirmed that it's correct. You 135 | can also add specific values as special cases to ignore. (These obviously 136 | depend on the specific core file you're debugging.) 137 | 138 | The intended workflow is that you run `mdbv8diff` on output from before and 139 | after your change. It likely finds tons of diffs. You create a small "ignore" 140 | module. As you go through the diffs one-by-one, if the diff represents a 141 | regression, you fix your code. If the diff represents an expected change, you 142 | teach your "ignore" module how to recognize it. You keep iterating this process 143 | until `mdbv8diff` produces no more output. 144 | 145 | This is obviously pretty crude. Think of this as a tool to assist the otherwise 146 | manual effort of exhaustively comparing lots of output, not an efficient 147 | regression tester. 148 | 149 | The "ignore" modules for past changes are committed to the repo for reference, 150 | though they likely wouldn't be reused except to reproduce the original testing 151 | work for the change. 152 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdb_v8_deps", 3 | "version": "1.0.0", 4 | "description": "package used only to install devDependencies for mdb_v8", 5 | "private": true, 6 | "devDependencies": { 7 | "jsprim": "^2.0.0", 8 | "vasync": "^2.2.0", 9 | "verror": "^1.10.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/mdb_v8_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_array.c: implementations of functions used for working with 13 | * JavaScript arrays. These are not to be confused with V8's internal 14 | * FixedArray (upon which JavaScript arrays are built). 15 | */ 16 | 17 | #include 18 | 19 | #include "v8dbg.h" 20 | #include "mdb_v8_dbg.h" 21 | #include "mdb_v8_impl.h" 22 | 23 | /* 24 | * See mdb_v8_dbg.h for details on what these various structures represent. 25 | */ 26 | struct v8array { 27 | uintptr_t v8array_addr; /* address in target proc */ 28 | int v8array_memflags; /* allocation flags */ 29 | v8fixedarray_t *v8array_elements; /* elements array */ 30 | size_t v8array_length; /* length */ 31 | }; 32 | 33 | /* 34 | * Private structure used for managing iteration over the array. 35 | */ 36 | typedef struct { 37 | v8array_t *v8ai_array; 38 | int (*v8ai_func)(v8array_t *, 39 | unsigned int, uintptr_t, void *); 40 | void *v8ai_uarg; 41 | int v8ai_rv; 42 | } v8array_iteration_t; 43 | 44 | 45 | /* 46 | * Load a JSArray object. 47 | * See the patterns in mdb_v8_dbg.h for interface details. 48 | */ 49 | v8array_t * 50 | v8array_load(uintptr_t addr, int memflags) 51 | { 52 | uint8_t type; 53 | uintptr_t length, elements; 54 | v8array_t *ap; 55 | 56 | if (!V8_IS_HEAPOBJECT(addr) || read_typebyte(&type, addr) != 0) { 57 | v8_warn("%p: not a heap object\n", addr); 58 | return (NULL); 59 | } 60 | 61 | if (type != V8_TYPE_JSARRAY) { 62 | v8_warn("%p: not a JSArray\n", addr); 63 | return (NULL); 64 | } 65 | 66 | if (read_heap_smi(&length, addr, V8_OFF_JSARRAY_LENGTH) != 0) { 67 | v8_warn("%p: could not read JSArray length\n", addr); 68 | return (NULL); 69 | } 70 | 71 | if (read_heap_ptr(&elements, addr, V8_OFF_JSOBJECT_ELEMENTS) != 0) { 72 | v8_warn("%p: could not read JSArray elements\n", addr); 73 | return (NULL); 74 | } 75 | 76 | if ((ap = mdb_zalloc(sizeof (*ap), memflags)) == NULL) { 77 | return (NULL); 78 | } 79 | 80 | ap->v8array_addr = addr; 81 | ap->v8array_length = length; 82 | ap->v8array_memflags = memflags; 83 | 84 | if (ap->v8array_length > 0) { 85 | ap->v8array_elements = v8fixedarray_load(elements, memflags); 86 | if (ap->v8array_elements == NULL) { 87 | v8array_free(ap); 88 | return (NULL); 89 | } 90 | } 91 | 92 | return (ap); 93 | } 94 | 95 | /* 96 | * See the patterns in mdb_v8_dbg.h for interface details. 97 | */ 98 | void 99 | v8array_free(v8array_t *ap) 100 | { 101 | if (ap == NULL) { 102 | return; 103 | } 104 | 105 | v8fixedarray_free(ap->v8array_elements); 106 | maybefree(ap, sizeof (*ap), ap->v8array_memflags); 107 | } 108 | 109 | size_t 110 | v8array_length(v8array_t *ap) 111 | { 112 | return (ap->v8array_length); 113 | } 114 | 115 | static int 116 | v8array_iter_one(v8fixedarray_t *arrayp, unsigned int index, 117 | uintptr_t value, void *uarg) 118 | { 119 | v8array_iteration_t *iterate_statep = uarg; 120 | 121 | /* 122 | * The JSArray may have fewer elements than the underlying FixedArray 123 | * that's used to store its contents. In that case, stop early. 124 | */ 125 | if (index >= iterate_statep->v8ai_array->v8array_length) { 126 | return (-1); 127 | } 128 | 129 | iterate_statep->v8ai_rv = (iterate_statep->v8ai_func( 130 | iterate_statep->v8ai_array, index, value, 131 | iterate_statep->v8ai_uarg)); 132 | return (iterate_statep->v8ai_rv == 0 ? 0 : -1); 133 | } 134 | 135 | int 136 | v8array_iter_elements(v8array_t *ap, 137 | int (*func)(v8array_t *, unsigned int, uintptr_t, void *), void *uarg) 138 | { 139 | v8array_iteration_t iterate_state; 140 | 141 | if (v8array_length(ap) == 0) { 142 | return (0); 143 | } 144 | 145 | iterate_state.v8ai_array = ap; 146 | iterate_state.v8ai_uarg = uarg; 147 | iterate_state.v8ai_func = func; 148 | iterate_state.v8ai_rv = 0; 149 | (void) v8fixedarray_iter_elements(ap->v8array_elements, 150 | v8array_iter_one, &iterate_state); 151 | return (iterate_state.v8ai_rv); 152 | } 153 | -------------------------------------------------------------------------------- /src/mdb_v8_dbi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_dbi.c: implementation of interfaces typically provided by the 13 | * surrounding debugger (i.e., mdb). Various low-level functions in mdb_v8.c 14 | * (e.g., read_heap_ptr() and related functions) ought to be polished and moved 15 | * into here. 16 | */ 17 | 18 | #include "mdb_v8_impl.h" 19 | 20 | #include 21 | 22 | /* 23 | * dbi_ugrep(addr, func, arg): find references to "addr" in the address space 24 | * and invoke "func" for each one. Specifically, scans all pointer-aligned 25 | * values in mapped memory and invokes "func" for each one whose value is 26 | * "addr". 27 | * 28 | * mdb provides a "::ugrep" dcmd that implements this sort of search (with much 29 | * more flexibility), but there's no way for us to use it here (except maybe via 30 | * an "::eval" that calls back into a private dcmd that we write). Since it's 31 | * not that complicated to begin with, we essentially reimplement it here. 32 | */ 33 | 34 | /* 35 | * Describes the state of a "ugrep" operation. 36 | */ 37 | typedef struct ugrep_op { 38 | uintptr_t ug_addr; /* address we're searching for */ 39 | int ug_result; /* ret code of the ugrep operation */ 40 | int (*ug_callback)(uintptr_t, void *); /* user cb */ 41 | void *ug_cbarg; /* user callback args */ 42 | uintptr_t *ug_buf; /* buffer for reading memory */ 43 | size_t ug_bufsz; /* size of "ug_buf" */ 44 | } ugrep_op_t; 45 | 46 | static int ugrep_mapping(ugrep_op_t *, const prmap_t *, const char *); 47 | 48 | int 49 | dbi_ugrep(uintptr_t addr, int (*callback)(uintptr_t, void *), void *cbarg) 50 | { 51 | struct ps_prochandle *Pr; 52 | ugrep_op_t ugrep; 53 | int err; 54 | 55 | if (mdb_get_xdata("pshandle", &Pr, sizeof (Pr)) == -1) { 56 | mdb_warn("couldn't read pshandle xdata"); 57 | return (-1); 58 | } 59 | 60 | ugrep.ug_addr = addr; 61 | ugrep.ug_result = 0; 62 | ugrep.ug_callback = callback; 63 | ugrep.ug_cbarg = cbarg; 64 | ugrep.ug_bufsz = 4096; 65 | ugrep.ug_buf = mdb_zalloc(ugrep.ug_bufsz, UM_SLEEP); 66 | 67 | err = Pmapping_iter(Pr, (proc_map_f *)ugrep_mapping, &ugrep); 68 | mdb_free(ugrep.ug_buf, ugrep.ug_bufsz); 69 | 70 | return (err != 0 ? -1 : ugrep.ug_result); 71 | } 72 | 73 | /* ARGSUSED */ 74 | static int 75 | ugrep_mapping(ugrep_op_t *ugrep, const prmap_t *pmp, const char *name) 76 | { 77 | uintptr_t chunkbase, vaddr; 78 | uintptr_t *buf; 79 | size_t ntoread, bufsz, nptrs, i; 80 | 81 | buf = ugrep->ug_buf; 82 | bufsz = ugrep->ug_bufsz; 83 | 84 | for (chunkbase = pmp->pr_vaddr; 85 | chunkbase < pmp->pr_vaddr + pmp->pr_size; chunkbase += bufsz) { 86 | ntoread = MIN(bufsz, 87 | pmp->pr_size - (chunkbase - pmp->pr_vaddr)); 88 | 89 | if (mdb_vread(buf, ntoread, chunkbase) == -1) { 90 | /* 91 | * Some mappings are not present in core files. This 92 | * does not represent an error case here. 93 | */ 94 | continue; 95 | } 96 | 97 | nptrs = ntoread / sizeof (uintptr_t); 98 | for (i = 0; i < nptrs; i++) { 99 | if (buf[i] == ugrep->ug_addr) { 100 | vaddr = chunkbase + (i * sizeof (uintptr_t)); 101 | ugrep->ug_result = ugrep->ug_callback(vaddr, 102 | ugrep->ug_cbarg); 103 | if (ugrep->ug_result != 0) { 104 | return (-1); 105 | } 106 | } 107 | } 108 | } 109 | 110 | return (0); 111 | } 112 | -------------------------------------------------------------------------------- /src/mdb_v8_dbi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_dbi.h: interfaces typically provided by the surrounding debugger 13 | * (i.e., mdb). 14 | */ 15 | 16 | #ifndef _MDBV8DBI_H 17 | #define _MDBV8DBI_H 18 | 19 | int dbi_ugrep(uintptr_t, int (*func)(uintptr_t, void *), void *); 20 | 21 | #endif /* _MDBV8DBI_H */ 22 | -------------------------------------------------------------------------------- /src/mdb_v8_impl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_impl.h: common functions used in the implementation of this module. 13 | * This file is a work in progress. For now, it contains both MDB-specific and 14 | * MDB-agnostic definitions, but the hope is to eventually make this 15 | * MDB-agnostic. 16 | */ 17 | 18 | #ifndef _MDBV8IMPL_H 19 | #define _MDBV8IMPL_H 20 | 21 | #include 22 | 23 | /* 24 | * We hard-code our MDB_API_VERSION to be 3 to allow this module to be 25 | * compiled on systems with higher version numbers, but still allow the 26 | * resulting binary object to be used on older systems. (We do not make use 27 | * of functionality present in versions later than 3.) This is particularly 28 | * important for mdb_v8 because (1) it's used in particular to debug 29 | * application-level software and (2) it has a history of rapid evolution. 30 | */ 31 | #define MDB_API_VERSION 3 32 | 33 | #include 34 | 35 | /* 36 | * XXX Cleanup work to be done on these: 37 | * - prefix these function names 38 | * - normalize their names, calling patterns, and argument types 39 | * - add the other related functions 40 | */ 41 | void maybefree(void *, size_t, int); 42 | int read_heap_array(uintptr_t, uintptr_t **, size_t *, int); 43 | int read_heap_maybesmi(uintptr_t *, uintptr_t, ssize_t); 44 | int read_heap_ptr(uintptr_t *, uintptr_t, ssize_t); 45 | int read_heap_smi(uintptr_t *, uintptr_t, ssize_t); 46 | int read_size(size_t *, uintptr_t); 47 | int read_typebyte(uint8_t *, uintptr_t); 48 | void v8_warn(const char *, ...); 49 | boolean_t jsobj_is_undefined(uintptr_t); 50 | 51 | /* 52 | * We need to find a better way of exposing this information. For now, these 53 | * represent all the metadata constants used by multiple C files. 54 | */ 55 | extern intptr_t V8_TYPE_FIXEDARRAY; 56 | extern intptr_t V8_TYPE_JSARRAY; 57 | extern intptr_t V8_TYPE_JSBOUNDFUNCTION; 58 | extern intptr_t V8_TYPE_JSFUNCTION; 59 | extern intptr_t V8_TYPE_JSOBJECT; 60 | extern intptr_t V8_TYPE_MAP; 61 | 62 | extern intptr_t V8_IsNotStringMask; 63 | extern intptr_t V8_StringTag; 64 | extern intptr_t V8_NotStringTag; 65 | extern intptr_t V8_StringEncodingMask; 66 | extern intptr_t V8_TwoByteStringTag; 67 | extern intptr_t V8_AsciiStringTag; 68 | extern intptr_t V8_OneByteStringTag; 69 | extern intptr_t V8_StringRepresentationMask; 70 | extern intptr_t V8_SeqStringTag; 71 | extern intptr_t V8_ConsStringTag; 72 | extern intptr_t V8_SlicedStringTag; 73 | extern intptr_t V8_ExternalStringTag; 74 | extern intptr_t V8_CompilerHints_BoundFunction; 75 | 76 | extern ssize_t V8_OFF_CODE_INSTRUCTION_SIZE; 77 | extern ssize_t V8_OFF_CODE_INSTRUCTION_START; 78 | extern ssize_t V8_OFF_CONSSTRING_FIRST; 79 | extern ssize_t V8_OFF_CONSSTRING_SECOND; 80 | extern ssize_t V8_OFF_EXTERNALSTRING_RESOURCE; 81 | extern ssize_t V8_OFF_FIXEDARRAY_DATA; 82 | extern ssize_t V8_OFF_FIXEDARRAY_LENGTH; 83 | extern ssize_t V8_OFF_HEAPOBJECT_MAP; 84 | extern ssize_t V8_OFF_JSARRAY_LENGTH; 85 | extern ssize_t V8_OFF_JSBOUNDFUNCTION_BOUND_ARGUMENTS; 86 | extern ssize_t V8_OFF_JSBOUNDFUNCTION_BOUND_TARGET_FUNCTION; 87 | extern ssize_t V8_OFF_JSBOUNDFUNCTION_BOUND_THIS; 88 | extern ssize_t V8_OFF_JSFUNCTION_CONTEXT; 89 | extern ssize_t V8_OFF_JSFUNCTION_LITERALS_OR_BINDINGS; 90 | extern ssize_t V8_OFF_JSFUNCTION_SHARED; 91 | extern ssize_t V8_OFF_JSOBJECT_ELEMENTS; 92 | extern ssize_t V8_OFF_MAP_INOBJECT_PROPERTIES; 93 | extern ssize_t V8_OFF_SCRIPT_LINE_ENDS; 94 | extern ssize_t V8_OFF_SCRIPT_NAME; 95 | extern ssize_t V8_OFF_SEQASCIISTR_CHARS; 96 | extern ssize_t V8_OFF_SEQONEBYTESTR_CHARS; 97 | extern ssize_t V8_OFF_SEQTWOBYTESTR_CHARS; 98 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_CODE; 99 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_COMPILER_HINTS; 100 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_SCOPE_INFO; 101 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_INFERRED_NAME; 102 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_IDENTIFIER; 103 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_FUNCTION_TOKEN_POSITION; 104 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_NAME; 105 | extern ssize_t V8_OFF_SHAREDFUNCTIONINFO_SCRIPT; 106 | extern ssize_t V8_OFF_SLICEDSTRING_PARENT; 107 | extern ssize_t V8_OFF_SLICEDSTRING_OFFSET; 108 | extern ssize_t V8_OFF_STRING_LENGTH; 109 | 110 | extern intptr_t V8_CONTEXT_IDX_CLOSURE; 111 | extern intptr_t V8_CONTEXT_IDX_EXT; 112 | extern intptr_t V8_CONTEXT_IDX_GLOBAL; 113 | extern intptr_t V8_CONTEXT_IDX_NATIVE; 114 | extern intptr_t V8_CONTEXT_IDX_PREV; 115 | extern intptr_t V8_CONTEXT_NCOMMON; 116 | 117 | extern intptr_t V8_SCOPEINFO_IDX_FIRST_VARS; 118 | extern intptr_t V8_SCOPEINFO_IDX_NCONTEXTLOCALS; 119 | extern intptr_t V8_SCOPEINFO_IDX_NPARAMS; 120 | extern intptr_t V8_SCOPEINFO_IDX_NSTACKLOCALS; 121 | extern intptr_t V8_SCOPEINFO_OFFSET_STACK_LOCALS; 122 | 123 | extern intptr_t V8_HeapObjectTag; 124 | extern intptr_t V8_HeapObjectTagMask; 125 | extern intptr_t V8_SmiTag; 126 | extern intptr_t V8_SmiTagMask; 127 | extern intptr_t V8_SmiValueShift; 128 | extern intptr_t V8_SmiShiftSize; 129 | 130 | /* see node_string.h */ 131 | #define NODE_OFF_EXTSTR_DATA sizeof (uintptr_t) 132 | 133 | #endif /* _MDBV8IMPL_H */ 134 | -------------------------------------------------------------------------------- /src/mdb_v8_strbuf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2015, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_strbuf.c: implementations of string functions. 13 | */ 14 | 15 | #include "mdb_v8_dbg.h" 16 | #include "mdb_v8_impl.h" 17 | 18 | #include 19 | #include 20 | 21 | typedef enum { 22 | MSB_NOALLOC = 0x1, /* stack-allocated strbuf */ 23 | } mdbv8_strbuf_flags_t; 24 | 25 | mdbv8_strbuf_t * 26 | mdbv8_strbuf_alloc(size_t nbytes, int memflags) 27 | { 28 | mdbv8_strbuf_t *strb; 29 | 30 | if ((strb = mdb_zalloc(sizeof (*strb), memflags)) == NULL || 31 | (strb->ms_buf = mdb_zalloc(nbytes, memflags)) == NULL) { 32 | maybefree(strb, sizeof (*strb), memflags); 33 | return (NULL); 34 | } 35 | 36 | strb->ms_bufsz = nbytes; 37 | strb->ms_memflags = memflags; 38 | strb->ms_reservesz = 0; 39 | mdbv8_strbuf_rewind(strb); 40 | return (strb); 41 | } 42 | 43 | void 44 | mdbv8_strbuf_free(mdbv8_strbuf_t *strb) 45 | { 46 | if (strb == NULL || (strb->ms_flags & MSB_NOALLOC) != 0) { 47 | return; 48 | } 49 | 50 | maybefree(strb->ms_buf, strb->ms_bufsz, strb->ms_memflags); 51 | maybefree(strb, sizeof (*strb), strb->ms_memflags); 52 | } 53 | 54 | void 55 | mdbv8_strbuf_init(mdbv8_strbuf_t *strb, char *buf, size_t bufsz) 56 | { 57 | bzero(strb, sizeof (*strb)); 58 | strb->ms_buf = buf; 59 | strb->ms_bufsz = bufsz; 60 | strb->ms_flags = MSB_NOALLOC; 61 | strb->ms_memflags = 0; 62 | strb->ms_reservesz = 0; 63 | mdbv8_strbuf_rewind(strb); 64 | } 65 | 66 | /* 67 | * This legacy interface is provided to transition code that passed around 68 | * arguments with the same types as "bufp" and "lenp" below. This code expects 69 | * that the pointed-to values will be changed as data is written to the stream. 70 | * This allows that code to be transitioned to using this class by using: 71 | * 72 | * mdbv8_strbuf_t strbuf; 73 | * mdbv8_strbuf_init(&strbuf, *bufp, *lenp); 74 | * 75 | * ... // calls that write strbuf 76 | * 77 | * mdbv8_strbuf_legacy_update(&strbuf, bufp, lenp); 78 | */ 79 | void 80 | mdbv8_strbuf_legacy_update(mdbv8_strbuf_t *strb, char **bufp, size_t *lenp) 81 | { 82 | *bufp = strb->ms_curbuf; 83 | *lenp = strb->ms_curbufsz; 84 | } 85 | 86 | size_t 87 | mdbv8_strbuf_bufsz(mdbv8_strbuf_t *strb) 88 | { 89 | return (strb->ms_bufsz); 90 | } 91 | 92 | size_t 93 | mdbv8_strbuf_bytesleft(mdbv8_strbuf_t *strb) 94 | { 95 | if (strb->ms_curbufsz - 1 < strb->ms_reservesz) 96 | return (0); 97 | 98 | return (strb->ms_curbufsz - 1 - strb->ms_reservesz); 99 | } 100 | 101 | void 102 | mdbv8_strbuf_rewind(mdbv8_strbuf_t *strb) 103 | { 104 | strb->ms_curbuf = strb->ms_buf; 105 | strb->ms_curbufsz = strb->ms_bufsz; 106 | strb->ms_curbuf[0] = '\0'; 107 | } 108 | 109 | void 110 | mdbv8_strbuf_reserve(mdbv8_strbuf_t *strb, ssize_t nbytes) 111 | { 112 | strb->ms_reservesz += nbytes; 113 | } 114 | 115 | void 116 | mdbv8_strbuf_appendc(mdbv8_strbuf_t *strb, uint16_t c, 117 | mdbv8_strappend_flags_t flags) 118 | { 119 | if ((flags & MSF_ASCIIONLY) != 0 && !isascii(c)) { 120 | c = '?'; 121 | } 122 | 123 | if ((flags & MSF_JSON) == MSF_JSON) { 124 | /* XXX validate this with JSON spec */ 125 | /* 126 | * This must be kept in sync with mdbv8_strbuf_nbytesforchar(). 127 | */ 128 | switch (c) { 129 | case '\b': 130 | mdbv8_strbuf_sprintf(strb, "\\b"); 131 | return; 132 | 133 | case '\n': 134 | mdbv8_strbuf_sprintf(strb, "\\n"); 135 | return; 136 | 137 | case '\r': 138 | mdbv8_strbuf_sprintf(strb, "\\r"); 139 | return; 140 | 141 | case '\\': 142 | mdbv8_strbuf_sprintf(strb, "\\\\"); 143 | return; 144 | 145 | case '"': 146 | mdbv8_strbuf_sprintf(strb, "\""); 147 | return; 148 | 149 | default: 150 | if (iscntrl(c)) { 151 | mdbv8_strbuf_sprintf(strb, "?"); 152 | return; 153 | } 154 | break; 155 | } 156 | } 157 | 158 | mdbv8_strbuf_sprintf(strb, "%c", c); 159 | } 160 | 161 | size_t 162 | mdbv8_strbuf_nbytesforchar(uint16_t c, mdbv8_strappend_flags_t flags) 163 | { 164 | if ((flags & MSF_JSON) == MSF_JSON) { 165 | /* 166 | * This must be kept in sync with mdbv8_strbuf_appendc(). 167 | */ 168 | switch (c) { 169 | case '\b': 170 | case '\n': 171 | case '\r': 172 | case '\\': 173 | case '"': 174 | return (2); 175 | 176 | default: 177 | break; 178 | } 179 | } 180 | 181 | return (1); 182 | } 183 | 184 | void 185 | mdbv8_strbuf_sprintf(mdbv8_strbuf_t *strb, const char *format, ...) 186 | { 187 | va_list alist; 188 | 189 | va_start(alist, format); 190 | mdbv8_strbuf_vsprintf(strb, format, alist); 191 | va_end(alist); 192 | } 193 | 194 | void 195 | mdbv8_strbuf_vsprintf(mdbv8_strbuf_t *strb, const char *format, va_list alist) 196 | { 197 | size_t rv, len; 198 | 199 | if (strb->ms_curbufsz <= strb->ms_reservesz) 200 | return; 201 | 202 | rv = vsnprintf(strb->ms_curbuf, strb->ms_curbufsz - strb->ms_reservesz, 203 | format, alist); 204 | len = MIN(rv, strb->ms_curbufsz - strb->ms_reservesz - 1); 205 | strb->ms_curbufsz -= len; 206 | strb->ms_curbuf += len; 207 | } 208 | 209 | const char * 210 | mdbv8_strbuf_tocstr(mdbv8_strbuf_t *strb) 211 | { 212 | return (strb->ms_buf); 213 | } 214 | 215 | void 216 | mdbv8_strbuf_appends(mdbv8_strbuf_t *strb, const char *src, 217 | mdbv8_strappend_flags_t flags) 218 | { 219 | size_t i, len; 220 | 221 | len = strlen(src); 222 | for (i = 0; i < len; i++) { 223 | mdbv8_strbuf_appendc(strb, src[i], flags); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/mdb_v8_subr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_subr.c: utility functions used internally within mdb_v8. 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | #include "v8dbg.h" 19 | #include "mdb_v8_dbg.h" 20 | #include "mdb_v8_impl.h" 21 | 22 | struct v8fixedarray { 23 | uintptr_t v8fa_addr; 24 | int v8fa_memflags; 25 | unsigned long v8fa_nelts; 26 | uintptr_t v8fa_elements; 27 | }; 28 | 29 | /* 30 | * Load a V8 FixedArray object. 31 | * See the patterns in mdb_v8_dbg.h for interface details. 32 | */ 33 | v8fixedarray_t * 34 | v8fixedarray_load(uintptr_t addr, int memflags) 35 | { 36 | uint8_t type; 37 | uintptr_t nelts; 38 | v8fixedarray_t *arrayp; 39 | 40 | if (!V8_IS_HEAPOBJECT(addr) || 41 | read_typebyte(&type, addr) != 0 || type != V8_TYPE_FIXEDARRAY || 42 | read_heap_smi(&nelts, addr, V8_OFF_FIXEDARRAY_LENGTH) != 0 || 43 | (arrayp = mdb_zalloc(sizeof (*arrayp), memflags)) == NULL) { 44 | return (NULL); 45 | } 46 | 47 | arrayp->v8fa_addr = addr; 48 | arrayp->v8fa_memflags = memflags; 49 | arrayp->v8fa_nelts = nelts; 50 | return (arrayp); 51 | } 52 | 53 | /* 54 | * Free a V8 FixedArray object. 55 | * See the patterns in mdb_v8_dbg.h for interface details. 56 | */ 57 | void 58 | v8fixedarray_free(v8fixedarray_t *arrayp) 59 | { 60 | if (arrayp == NULL) { 61 | return; 62 | } 63 | 64 | maybefree(arrayp, sizeof (*arrayp), arrayp->v8fa_memflags); 65 | } 66 | 67 | /* 68 | * Iterate the elements of this fixed array. 69 | * 70 | * This implementation is careful to avoid needing memory proportional to the 71 | * array size, as that makes it very difficult for end users to work with very 72 | * large arrays. 73 | */ 74 | int 75 | v8fixedarray_iter_elements(v8fixedarray_t *arrayp, 76 | int (*func)(v8fixedarray_t *, unsigned int, uintptr_t, void *), 77 | void *uarg) 78 | { 79 | int maxnpgelts = 1024; 80 | int curnpgelts; 81 | uintptr_t *buf; 82 | uintptr_t addr; 83 | unsigned int index, length, i; 84 | size_t maxpgsz, curpgsz; 85 | int rv = -1; 86 | 87 | length = v8fixedarray_length(arrayp); 88 | if (length == 0) { 89 | return (0); 90 | } 91 | 92 | maxpgsz = maxnpgelts * sizeof (buf[0]); 93 | buf = alloca(maxpgsz); 94 | 95 | addr = arrayp->v8fa_addr + V8_OFF_FIXEDARRAY_DATA; 96 | index = 0; 97 | 98 | do { 99 | curnpgelts = MIN(length - index, maxnpgelts); 100 | curpgsz = curnpgelts * sizeof (buf[0]); 101 | rv = mdb_vread(buf, curpgsz, addr); 102 | if (rv == -1) { 103 | v8_warn("failed to read array from index %d", index); 104 | break; 105 | } 106 | 107 | for (i = 0; i < curnpgelts; i++) { 108 | rv = func(arrayp, index + i, buf[i], uarg); 109 | if (rv != 0) { 110 | break; 111 | } 112 | } 113 | 114 | index += i; 115 | addr += curpgsz; 116 | } while (rv == 0 && index < length - 1); 117 | 118 | return (rv); 119 | } 120 | 121 | /* 122 | * Return a native array representing the contents of the FixedArray "arrayp". 123 | * NOTE: If possible, v8fixedarray_iter_elements() should be used instead of 124 | * this function. That function requires only a constant amount of memory 125 | * regardless of the array size. That can be a major performance improvement 126 | * when available memory is limited, and it makes it possible to work with 127 | * arbitrarily large arrays for which we can't necessarily allocate such a large 128 | * block. 129 | * 130 | * The length of the returned array is given by v8fixedarray_length(). The 131 | * caller must free this with "maybefree" using the same "memflags". 132 | * TODO This should probably either be a separate type, or maybe we should hang 133 | * it off the v8fixedarray_t (and keep it cached). 134 | */ 135 | uintptr_t * 136 | v8fixedarray_as_array(v8fixedarray_t *arrayp, int memflags) 137 | { 138 | uintptr_t *elts; 139 | size_t arraysz; 140 | 141 | if (arrayp->v8fa_nelts == 0) { 142 | return (NULL); 143 | } 144 | 145 | arraysz = arrayp->v8fa_nelts * sizeof (elts[0]); 146 | elts = mdb_zalloc(arraysz, memflags); 147 | if (elts == NULL) { 148 | return (NULL); 149 | } 150 | 151 | if (mdb_vread(elts, arraysz, 152 | arrayp->v8fa_addr + V8_OFF_FIXEDARRAY_DATA) == -1) { 153 | maybefree(elts, arraysz, memflags); 154 | return (NULL); 155 | } 156 | 157 | return (elts); 158 | } 159 | 160 | /* 161 | * Returns the number of elements in the FixedArray "arrayp". 162 | */ 163 | size_t 164 | v8fixedarray_length(v8fixedarray_t *arrayp) 165 | { 166 | return (arrayp->v8fa_nelts); 167 | } 168 | 169 | /* 170 | * Attempts to determine whether the object at "addr" might contain the address 171 | * "target". This is used for low-level heuristic analysis. Note that it's 172 | * possible that we cannot tell whether the address is contained (e.g., if this 173 | * is a variable-length object and we can't read how big it is). 174 | */ 175 | int 176 | v8contains(uintptr_t addr, uint8_t type, uintptr_t target, 177 | boolean_t *containsp) 178 | { 179 | size_t size; 180 | uintptr_t objsize; 181 | 182 | /* 183 | * For sequential strings, we need to look at how many characters there 184 | * are, and how many bytes per character are used to encode the string. 185 | * For other types of strings, the V8 heap object is not variable-sized, 186 | * so we can treat it like the other cases below. 187 | */ 188 | if (V8_TYPE_STRING(type) && V8_STRREP_SEQ(type)) { 189 | v8string_t *strp; 190 | size_t length; 191 | 192 | if ((strp = v8string_load(addr, UM_SLEEP)) == NULL) { 193 | return (-1); 194 | } 195 | 196 | length = v8string_length(strp); 197 | 198 | if (V8_STRENC_ASCII(type)) { 199 | size = V8_OFF_SEQASCIISTR_CHARS + length; 200 | } else { 201 | size = V8_OFF_SEQTWOBYTESTR_CHARS + (2 * length); 202 | } 203 | 204 | v8string_free(strp); 205 | *containsp = target < addr + size; 206 | return (0); 207 | } 208 | 209 | if (type == V8_TYPE_FIXEDARRAY) { 210 | v8fixedarray_t *arrayp; 211 | size_t length; 212 | 213 | if ((arrayp = v8fixedarray_load(addr, UM_SLEEP)) == NULL) { 214 | return (-1); 215 | } 216 | 217 | length = v8fixedarray_length(arrayp); 218 | size = V8_OFF_FIXEDARRAY_DATA + length * sizeof (uintptr_t); 219 | v8fixedarray_free(arrayp); 220 | *containsp = target < addr + size; 221 | return (0); 222 | } 223 | 224 | if (read_size(&objsize, addr) != 0) { 225 | return (-1); 226 | } 227 | 228 | size = objsize; 229 | if (type == V8_TYPE_JSOBJECT) { 230 | /* 231 | * Instances of JSObject can also contain a number of property 232 | * values directly in the object. To find out how many, we need 233 | * to read the count out of the map. See jsobj_properties() for 234 | * details on how this works. 235 | */ 236 | uintptr_t map; 237 | uint8_t ninprops; 238 | if (mdb_vread(&map, sizeof (map), 239 | addr + V8_OFF_HEAPOBJECT_MAP) == -1) { 240 | return (-1); 241 | } 242 | 243 | if (mdb_vread(&ninprops, sizeof (ninprops), 244 | map + V8_OFF_MAP_INOBJECT_PROPERTIES) == -1) { 245 | return (-1); 246 | } 247 | 248 | size += ninprops * sizeof (uintptr_t); 249 | } 250 | 251 | *containsp = target < addr + size; 252 | return (0); 253 | } 254 | -------------------------------------------------------------------------------- /src/mdb_v8_version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2015, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_version.h: declares version number values 13 | */ 14 | 15 | #ifndef _MDBV8_VERSION_H 16 | #define _MDBV8_VERSION_H 17 | 18 | /* 19 | * These constants are defined in mdb_v8_version.c, which is generated as part 20 | * of the build. 21 | */ 22 | extern int mdbv8_vers_major; 23 | extern int mdbv8_vers_minor; 24 | extern int mdbv8_vers_micro; 25 | 26 | #endif /* _MDBV8_VERSION_H */ 27 | -------------------------------------------------------------------------------- /src/mdb_v8_whatis.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * mdb_v8_whatis.c: implementation of "v8whatis" functionality. 13 | */ 14 | 15 | #include 16 | 17 | #include "v8dbg.h" 18 | #include "mdb_v8_dbg.h" 19 | #include "mdb_v8_impl.h" 20 | 21 | /* 22 | * v8whatis() attempts to find the V8 heap object that contains "addr" by 23 | * looking at up to "maxoffset" bytes leading up to "addr" for the specific 24 | * signature that indicates a V8 heap object, and then interpreting any possible 25 | * heap object to see if the target address is indeed contained within it. 26 | * Results are stored into "whatisp", and any errors are returned as a 27 | * "v8whatis_error_t". Note that as many fields of "whatisp" are populated as 28 | * possible, so even if you get V8W_ERR_DOESNTCONTAIN (which indicates that we 29 | * found an object, but it doesn't seem to contain the target), then 30 | * v8w_baseaddr and v8w_basetype are still valid. 31 | */ 32 | v8whatis_error_t 33 | v8whatis(uintptr_t addr, size_t maxoffset, v8whatis_t *whatisp) 34 | { 35 | uintptr_t origaddr, curaddr, curvalue, ptrlowbits; 36 | size_t curoffset; 37 | boolean_t contained; 38 | uint8_t typebyte; 39 | 40 | origaddr = addr; 41 | whatisp->v8w_origaddr = origaddr; 42 | 43 | /* 44 | * Objects will always be stored at pointer-aligned addresses. If we're 45 | * given an address that's not pointer-aligned, clear the low bits to 46 | * find the pointer-sized value containing the address given. 47 | */ 48 | ptrlowbits = sizeof (uintptr_t) - 1; 49 | addr &= ~ptrlowbits; 50 | assert(addr <= origaddr && origaddr - addr < sizeof (uintptr_t)); 51 | 52 | /* 53 | * On top of that, set the heap object tag bits. Recall that most 54 | * mdb_v8 operations interpret values the same way as V8: if the tag 55 | * bits are set, then this is a heap object; otherwise, it's not. And 56 | * this command only makes sense for heap objects, so one might expect 57 | * that we would bail if we're given something else. But in practice, 58 | * this command is expected to be chained with `::ugrep` or some other 59 | * command that reports heap objects without the tag bits set, so it 60 | * makes sense to just assume they were supposed to be set. 61 | */ 62 | addr |= V8_HeapObjectTag; 63 | whatisp->v8w_addr = addr; 64 | 65 | /* 66 | * At this point, we walk backwards from the address we're given looking 67 | * for something that looks like a V8 heap object. 68 | */ 69 | for (curoffset = 0; curoffset < maxoffset; 70 | curoffset += sizeof (uintptr_t)) { 71 | curaddr = addr - curoffset; 72 | assert(V8_IS_HEAPOBJECT(curaddr)); 73 | 74 | if (read_heap_ptr(&curvalue, curaddr, 75 | V8_OFF_HEAPOBJECT_MAP) != 0 || 76 | read_typebyte(&typebyte, curvalue) != 0) { 77 | /* 78 | * The address we're looking at was either unreadable, 79 | * or we could not follow its Map pointer to find the 80 | * type byte. This cannot be a valid heap object 81 | * because every heap object has a Map pointer as its 82 | * first field. 83 | */ 84 | continue; 85 | } 86 | 87 | if (typebyte != V8_TYPE_MAP) { 88 | /* 89 | * The address we're looking at refers to something 90 | * other than a Map. Again, this cannot be the address 91 | * of a valid heap object. 92 | */ 93 | continue; 94 | } 95 | 96 | /* 97 | * We've found what looks like a valid Map object. See if we 98 | * can read its type byte, too. If not, this is likely garbage. 99 | */ 100 | if (read_typebyte(&typebyte, curaddr) != 0) { 101 | continue; 102 | } 103 | 104 | break; 105 | } 106 | 107 | if (curoffset >= maxoffset) { 108 | return (V8W_ERR_NOTFOUND); 109 | } 110 | 111 | whatisp->v8w_baseaddr = curaddr; 112 | whatisp->v8w_basetype = typebyte; 113 | 114 | /* 115 | * At this point, check to see if the address that we were given might 116 | * be contained in this object. If not, that means we found a Map for a 117 | * heap object that doesn't contain our target address. We could have 118 | * checked this in the loop above so that we'd keep walking backwards in 119 | * this case, but we assume that Map objects aren't likely to appear 120 | * inside the middle of other valid objects, and thus that if we found a 121 | * Map and its heap object doesn't contain our target address, then 122 | * we're done -- there is no heap object containing our target. 123 | */ 124 | if (v8contains(curaddr, typebyte, addr, &contained) == 0 && 125 | !contained) { 126 | return (V8W_ERR_DOESNTCONTAIN); 127 | } 128 | 129 | return (V8W_OK); 130 | } 131 | -------------------------------------------------------------------------------- /src/v8cfg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2012, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * v8cfg.h: canned configurations for previous V8 versions 13 | */ 14 | 15 | #ifndef V8CFG_H 16 | #define V8CFG_H 17 | 18 | #include 19 | #include 20 | 21 | typedef struct { 22 | const char *v8cs_name; /* symbol name */ 23 | intptr_t v8cs_value; /* symbol value */ 24 | } v8_cfg_symbol_t; 25 | 26 | typedef struct v8_cfg { 27 | const char *v8cfg_name; /* canned config name */ 28 | const char *v8cfg_label; /* description */ 29 | v8_cfg_symbol_t *v8cfg_symbols; /* actual symbol values */ 30 | 31 | int (*v8cfg_iter)(struct v8_cfg *, int (*)(mdb_symbol_t *, void *), 32 | void *); 33 | int (*v8cfg_readsym)(struct v8_cfg *, const char *, intptr_t *); 34 | } v8_cfg_t; 35 | 36 | extern v8_cfg_t v8_cfg_04; 37 | extern v8_cfg_t v8_cfg_06; 38 | extern v8_cfg_t v8_cfg_target; 39 | extern v8_cfg_t *v8_cfgs[]; 40 | 41 | #endif /* V8CFG_H */ 42 | -------------------------------------------------------------------------------- /src/v8dbg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2017, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * v8dbg.h: macros for use by V8 heap inspection tools. The consumer must 13 | * define values for various tags and shifts. The MDB module gets these 14 | * constants from information encoded in the binary itself. 15 | */ 16 | 17 | #ifndef _V8DBG_H 18 | #define _V8DBG_H 19 | 20 | /* 21 | * Recall that while V8 heap objects are always 4-byte aligned, heap object 22 | * pointers always have the last bit set. So when looking for a field nominally 23 | * at offset X, one must be sure to clear the tag bit first. 24 | */ 25 | #define V8_OFF_HEAP(x) ((x) - V8_HeapObjectTag) 26 | 27 | /* 28 | * Determine whether a given pointer refers to a SMI, Failure, or HeapObject. 29 | */ 30 | #define V8_IS_SMI(ptr) (((ptr) & V8_SmiTagMask) == V8_SmiTag) 31 | #define V8_IS_FAILURE(ptr) (V8_FailureTagMask != -1 && \ 32 | V8_FailureTagMask != -1 && \ 33 | ((ptr) & V8_FailureTagMask) == V8_FailureTag) 34 | 35 | #define V8_IS_HEAPOBJECT(ptr) \ 36 | (((ptr) & V8_HeapObjectTagMask) == V8_HeapObjectTag) 37 | 38 | /* 39 | * Extract the value of a SMI "pointer". Recall that small integers are stored 40 | * using the upper 31 bits. 41 | */ 42 | #define V8_SMI_VALUE(smi) ((smi) >> (V8_SmiValueShift + V8_SmiShiftSize)) 43 | #define V8_VALUE_SMI(value) \ 44 | ((value) << (V8_SmiValueShift + V8_SmiShiftSize)) 45 | 46 | /* 47 | * Check compiler hints, which hang off of SharedFunctionInfo objects. 48 | */ 49 | #define V8_HINT_ISSET(hints, whichbit) \ 50 | (((hints) & (1 << (whichbit))) != 0) 51 | #define V8_HINT_BOUND(hints) \ 52 | (V8_HINT_ISSET((hints), V8_CompilerHints_BoundFunction)) 53 | 54 | /* 55 | * Determine the encoding and representation of a V8 string. 56 | */ 57 | #define V8_TYPE_STRING(type) (((type) & V8_IsNotStringMask) == V8_StringTag) 58 | 59 | #define V8_STRENC_ASCII(type) \ 60 | ((V8_AsciiStringTag != -1 && \ 61 | ((type) & V8_StringEncodingMask) == V8_AsciiStringTag) || \ 62 | (V8_OneByteStringTag != -1 && \ 63 | ((type) & V8_StringEncodingMask) == V8_OneByteStringTag)) 64 | 65 | #define V8_STRREP_SEQ(type) \ 66 | (((type) & V8_StringRepresentationMask) == V8_SeqStringTag) 67 | #define V8_STRREP_CONS(type) \ 68 | (((type) & V8_StringRepresentationMask) == V8_ConsStringTag) 69 | #define V8_STRREP_SLICED(type) \ 70 | (((type) & V8_StringRepresentationMask) == V8_SlicedStringTag) 71 | #define V8_STRREP_EXT(type) \ 72 | (((type) & V8_StringRepresentationMask) == V8_ExternalStringTag) 73 | 74 | /* 75 | * Several of the following constants and transformations are hardcoded in V8 as 76 | * well, so there's no way to extract them programmatically from the binary. 77 | */ 78 | #define V8_DESC_KEYIDX(x) ((x) + V8_PROP_IDX_FIRST) 79 | #define V8_DESC_VALIDX(x) ((x) << 1) 80 | #define V8_DESC_DETIDX(x) (((x) << 1) + 1) 81 | 82 | #define V8_DESC_ISFIELD(x) \ 83 | ((V8_SMI_VALUE(x) & V8_PROP_TYPE_MASK) == V8_PROP_TYPE_FIELD) 84 | 85 | #define V8_PROP_FIELDINDEX(value) \ 86 | ((V8_SMI_VALUE(value) & V8_PROPINDEX_MASK) >> V8_PROPINDEX_SHIFT) 87 | 88 | #endif /* _V8DBG_H */ 89 | -------------------------------------------------------------------------------- /test/lib/buffer-commands.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var getRuntimeVersions = require('./runtime-versions').getRuntimeVersions; 4 | 5 | function getFindBufferAddressCmd(opts) { 6 | assert.ok(typeof opts === 'object'); 7 | assert.ok(typeof opts.propertyName === 'string'); 8 | assert.ok(typeof opts.length === 'number'); 9 | assert.ok(typeof opts.outputFile === 'string'); 10 | 11 | var runtimeVersions = getRuntimeVersions(); 12 | 13 | // Starting from node v4.0, buffers are actually Uint8Array instances, 14 | // and they don't have a "length" property 15 | if (runtimeVersions.node.major < 4) { 16 | return '::findjsobjects -p ' + opts.propertyName + 17 | ' | ' + '::findjsobjects | ' + '::jsprint -b length ! ' + 18 | 'awk -F: \'$2 == ' + opts.length + 19 | '{ print $1 }\'' + '| head -1 > ' + opts.outputFile + '\n'; 20 | } else { 21 | return '::findjsobjects -p ' + opts.propertyName + 22 | ' | ' + '::findjsobjects | ' + '::jsprint -b ! ' + 23 | 'awk -F: \'index($2, "length ' + opts.length + '>") > 0 ' + 24 | '{ print $1 }\'' + '| head -1 > ' + opts.outputFile + '\n'; 25 | } 26 | } 27 | 28 | module.exports = { 29 | getFindBufferAddressCmd: getFindBufferAddressCmd 30 | }; 31 | -------------------------------------------------------------------------------- /test/lib/runtime-versions.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var cachedVersions = {}; 4 | 5 | function getNodeVersions() { 6 | var NODE_VERSIONS; 7 | var NODE_MAJOR; 8 | var NODE_MINOR; 9 | var NODE_PATCH; 10 | 11 | if (cachedVersions.node) { 12 | return cachedVersions.node; 13 | } 14 | 15 | NODE_VERSIONS = 16 | process.versions.node.match(/^(\d+)\.(\d+)\.(\d+)(\-\w+)?$/); 17 | assert.ok(NODE_VERSIONS); 18 | 19 | NODE_MAJOR = Number(NODE_VERSIONS[1]); 20 | assert.equal(isNaN(NODE_MAJOR), false); 21 | 22 | NODE_MINOR = Number(NODE_VERSIONS[2]); 23 | assert.equal(isNaN(NODE_MINOR), false); 24 | 25 | NODE_PATCH = Number(NODE_VERSIONS[3]); 26 | assert.equal(isNaN(NODE_PATCH), false); 27 | 28 | cachedVersions.node = { 29 | major: NODE_MAJOR, 30 | minor: NODE_MINOR, 31 | patch: NODE_PATCH 32 | }; 33 | 34 | return cachedVersions.node; 35 | } 36 | 37 | function getV8Versions() { 38 | var V8_VERSIONS; 39 | var V8_MAJOR; 40 | var V8_MINOR; 41 | var V8_BUILD; 42 | var V8_PATCH; 43 | 44 | if (cachedVersions.V8) { 45 | return cachedVersions.V8; 46 | } 47 | 48 | V8_VERSIONS = process.versions.v8.split('.'); 49 | 50 | V8_MAJOR = Number(V8_VERSIONS[0]); 51 | assert.equal(isNaN(V8_MAJOR), false); 52 | 53 | V8_MINOR = Number(V8_VERSIONS[1]); 54 | assert.equal(isNaN(V8_MINOR), false); 55 | 56 | V8_BUILD = Number(V8_VERSIONS[2]); 57 | assert.equal(isNaN(V8_BUILD), false); 58 | 59 | V8_PATCH = Number(V8_VERSIONS[3]); 60 | assert.equal(isNaN(V8_PATCH), false); 61 | 62 | cachedVersions.V8 = { 63 | major: V8_MAJOR, 64 | minor: V8_MINOR, 65 | build: V8_BUILD, 66 | patch: V8_PATCH 67 | }; 68 | 69 | return cachedVersions.V8; 70 | } 71 | 72 | function getRuntimeVersions() { 73 | return { 74 | node: getNodeVersions(), 75 | V8: getV8Versions() 76 | }; 77 | } 78 | 79 | /* 80 | * Compares two objects representing V8 versions. Returns 1 if versionA > 81 | * versionB, -1 if versionA < versionB and 0 otherwise. 82 | */ 83 | function compareV8Versions(versionA, versionB) { 84 | assert(typeof (versionA) === 'object', 'versionA must be an object'); 85 | assert(typeof (versionA.major) === 'number', 86 | 'versionA.major must be a number'); 87 | assert(typeof (versionA.minor) === 'number', 88 | 'versionA.minor must be a number'); 89 | assert(typeof (versionA.patch) === 'number', 90 | 'versionA.patch must be a number'); 91 | assert(versionA.build === undefined || typeof (versionA.build) === 'number', 92 | 'versionA.build must be a number or undefined'); 93 | 94 | assert(typeof (versionB) === 'object', 'versionB must be an object'); 95 | assert(typeof (versionB.major) === 'number', 96 | 'versionB.major must be a number'); 97 | assert(typeof (versionB.minor) === 'number', 98 | 'versionB.minor must be a number'); 99 | assert(typeof (versionB.patch) === 'number', 100 | 'versionB.patch must be a number'); 101 | assert(versionB.build === undefined || typeof (versionB.build) === 'number', 102 | 'versionB.build must be a number'); 103 | 104 | var versionLevelIndex; 105 | var versionLevels = ['major', 'minor', 'patch', 'build']; 106 | 107 | for (versionLevelIndex in versionLevels) { 108 | var versionLevel = versionLevels[versionLevelIndex]; 109 | if (versionA[versionLevel] !== undefined && 110 | versionB[versionLevel] !== undefined && 111 | versionA[versionLevel] != versionB[versionLevel]) { 112 | return versionA[versionLevel] > versionB[versionLevel] ? 1 : -1; 113 | } 114 | } 115 | 116 | return 0; 117 | } 118 | 119 | module.exports = { 120 | getNodeVersions: getNodeVersions, 121 | getV8Versions: getV8Versions, 122 | getRuntimeVersions: getRuntimeVersions, 123 | compareV8Versions: compareV8Versions 124 | }; 125 | -------------------------------------------------------------------------------- /test/standalone/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * test/standalone/common.js: common functions for standalone JavaScript tests 13 | */ 14 | 15 | var assert = require('assert'); 16 | var childprocess = require('child_process'); 17 | var events = require('events'); 18 | var fs = require('fs'); 19 | var path = require('path'); 20 | var util = require('util'); 21 | var vasync = require('vasync'); 22 | var VError = require('verror'); 23 | 24 | var gcoreSelf = require('./gcore_self'); 25 | 26 | /* Public interface */ 27 | exports.dmodpath = dmodpath; 28 | exports.createMdbSession = createMdbSession; 29 | exports.finalizeTestObject = finalizeTestObject; 30 | exports.standaloneTest = standaloneTest; 31 | exports.findTestObject = findTestObject; 32 | exports.splitMdbLines = splitMdbLines; 33 | 34 | var MDB_SENTINEL = 'MDB_SENTINEL'; 35 | 36 | /* 37 | * Returns the path to the built dmod, for loading into mdb during testing. 38 | */ 39 | function dmodpath() 40 | { 41 | var arch = process.arch == 'x64' ? 'amd64' : 'ia32'; 42 | return (path.join( 43 | __dirname, '..', '..', 'build', arch, 'mdb_v8.so')); 44 | } 45 | 46 | function MdbSession() 47 | { 48 | this.mdb_child = null; /* child process handle */ 49 | this.mdb_target_name = null; /* file name or pid */ 50 | this.mdb_args = []; /* extra CLI arguments */ 51 | this.mdb_target_type = null; /* "file" or "pid" */ 52 | this.mdb_remove_on_success = false; 53 | 54 | /* information about current pending command */ 55 | this.mdb_pending_cmd = null; 56 | this.mdb_pending_callback = null; 57 | 58 | /* runtime state */ 59 | this.mdb_exited = false; 60 | this.mdb_error = null; 61 | this.mdb_stdout = ''; /* buffered stdout */ 62 | this.mdb_stderr = ''; /* buffered stderr */ 63 | this.mdb_findleaks = null; 64 | } 65 | 66 | util.inherits(MdbSession, events.EventEmitter); 67 | 68 | MdbSession.prototype.runCmd = function (str, callback) 69 | { 70 | assert.equal(typeof (str), 'string'); 71 | assert.equal(typeof (callback), 'function'); 72 | assert.strictEqual(this.mdb_pending_cmd, null, 73 | 'command is already pending'); 74 | assert.strictEqual(this.mdb_error, null, 75 | 'already experienced fatal error'); 76 | assert.strictEqual(this.mdb_exited, false, 77 | 'mdb already exited'); 78 | assert.equal(str.charAt(str.length - 1), '\n', 79 | 'command string must end in a newline'); 80 | 81 | assert.strictEqual(this.mdb_pending_callback, null); 82 | this.mdb_pending_cmd = str; 83 | this.mdb_pending_callback = callback; 84 | process.stderr.write('> ' + str); 85 | this.mdb_child.stdin.write(str); 86 | this.mdb_child.stdin.write('!printf ' + MDB_SENTINEL + '; '); 87 | this.mdb_child.stdin.write('!printf ' + MDB_SENTINEL + ' >&2\n'); 88 | }; 89 | 90 | MdbSession.prototype.onExit = function (code) 91 | { 92 | this.mdb_exited = true; 93 | if (code !== 0) { 94 | this.mdb_error = new Error( 95 | 'mdb exited unexpectedly with code ' + code); 96 | this.emit('error', this.mdb_error); 97 | } 98 | }; 99 | 100 | MdbSession.prototype.doWork = function () 101 | { 102 | var outi, outchunk; 103 | var erri, errchunk; 104 | var callback; 105 | 106 | outi = this.mdb_stdout.indexOf(MDB_SENTINEL); 107 | erri = this.mdb_stderr.indexOf(MDB_SENTINEL); 108 | if (outi == -1 || erri == -1) { 109 | return; 110 | } 111 | 112 | outchunk = this.mdb_stdout.substr(0, outi); 113 | this.mdb_stdout = this.mdb_stdout.substr(outi + MDB_SENTINEL.length); 114 | console.error(outchunk); 115 | 116 | errchunk = this.mdb_stderr.substr(0, erri); 117 | this.mdb_stderr = this.mdb_stderr.substr(erri + MDB_SENTINEL.length); 118 | 119 | if (errchunk.length > 0) { 120 | console.log('mdb: stderr: ' + errchunk); 121 | } 122 | 123 | assert.notStrictEqual(this.mdb_pending_cmd, null); 124 | assert.notStrictEqual(this.mdb_pending_callback, null); 125 | callback = this.mdb_pending_callback; 126 | this.mdb_pending_callback = null; 127 | this.mdb_pending_cmd = null; 128 | callback(outchunk, errchunk); 129 | }; 130 | 131 | MdbSession.prototype.finish = function (error) 132 | { 133 | assert.strictEqual(this.mdb_error, null, 134 | 'already experienced fatal error'); 135 | process.removeListener('exit', this.mdb_onprocexit); 136 | 137 | if (!this.mdb_exited) { 138 | this.mdb_child.stdin.end(); 139 | } 140 | 141 | if (error) { 142 | this.mdb_error = new VError(error, 'error running test'); 143 | return; 144 | } 145 | 146 | if (this.mdb_remove_on_success && this.mdb_target_type == 'file') { 147 | fs.unlinkSync(this.mdb_target_name); 148 | } 149 | }; 150 | 151 | MdbSession.prototype.checkMdbLeaks = function (callback) 152 | { 153 | var self = this; 154 | var leakmdb; 155 | 156 | /* 157 | * Attach another MDB session to MDB itself so that we can run 158 | * "findleaks" to look for leaks in "mdb" and "mdb_v8". At this point, 159 | * we only print this information out, rather than trying to parse it 160 | * and take action. We also rely on the underlying MdbSession's 161 | * transcript rather than explicitly printing out the output. 162 | */ 163 | assert.strictEqual(this.mdb_findleaks, null, 164 | 'mdb leak check already pending'); 165 | leakmdb = this.mdb_findleaks = createMdbSession({ 166 | 'targetType': 'pid', 167 | 'targetName': this.mdb_child.pid.toString(), 168 | 'loadDmod': false, 169 | 'removeOnSuccess': false 170 | }, function (err) { 171 | assert.equal(self.mdb_findleaks, leakmdb); 172 | 173 | if (err) { 174 | self.mdb_findleaks = null; 175 | callback(new VError(err, 'attaching mdb to itself')); 176 | return; 177 | } 178 | 179 | leakmdb.runCmd('::findleaks -d\n', function () { 180 | assert.equal(self.mdb_findleaks, leakmdb); 181 | self.mdb_findleaks = null; 182 | leakmdb.finish(); 183 | callback(); 184 | }); 185 | }); 186 | }; 187 | 188 | /* 189 | * Opens an MDB session. Use runCmd() to invoke a command and get output. 190 | */ 191 | function createMdbSessionFile(filename, callback) 192 | { 193 | var envval, rmcore; 194 | 195 | envval = process.env['MDBV8_TEST_KEEPCORE']; 196 | if (envval !== undefined) { 197 | envval = envval.toLowerCase(); 198 | rmcore = envval != 'true' && envval != '1' && 199 | envval != 'yes'; 200 | } else { 201 | rmcore = true; 202 | } 203 | 204 | return (createMdbSession({ 205 | 'targetType': 'file', 206 | 'targetName': filename, 207 | 'loadDmod': true, 208 | 'removeOnSuccess': rmcore 209 | }, callback)); 210 | } 211 | 212 | function createMdbSession(args, callback) 213 | { 214 | var mdb, loaddmod; 215 | 216 | assert.equal('object', typeof (args)); 217 | assert.equal('string', typeof (args.targetType)); 218 | assert.equal('string', typeof (args.targetName)); 219 | assert.equal('boolean', typeof (args.removeOnSuccess)); 220 | assert.equal('boolean', typeof (args.loadDmod)); 221 | 222 | assert.ok(args.targetType == 'file' || args.targetType == 'pid'); 223 | loaddmod = args.loadDmod; 224 | 225 | mdb = new MdbSession(); 226 | mdb.mdb_target_name = args.targetName; 227 | mdb.mdb_target_type = args.targetType; 228 | mdb.mdb_remove_on_success = args.removeOnSuccess; 229 | 230 | /* Use the "-S" flag to avoid interference from a user's .mdbrc file. */ 231 | mdb.mdb_args.push('-S'); 232 | 233 | if (process.env['MDB_LIBRARY_PATH'] && 234 | process.env['MDB_LIBRARY_PATH'] != '') { 235 | mdb.mdb_args.push('-L'); 236 | mdb.mdb_args.push(process.env['MDB_LIBRARY_PATH']); 237 | } 238 | 239 | if (args.targetType == 'file') { 240 | mdb.mdb_args.push(args.targetName); 241 | } else { 242 | mdb.mdb_args.push('-p'); 243 | mdb.mdb_args.push(args.targetName); 244 | } 245 | 246 | mdb.mdb_child = childprocess.spawn('mdb', 247 | mdb.mdb_args, { 248 | 'stdio': 'pipe', 249 | 'env': { 250 | 'TZ': 'utc', 251 | 'UMEM_DEBUG': 'default', 252 | 'UMEM_LOGGING': 'transaction=8M,fail' 253 | } 254 | }); 255 | 256 | mdb.mdb_child.on('exit', function (code) { 257 | mdb.onExit(code); 258 | }); 259 | 260 | mdb.mdb_child.stdout.on('data', function (chunk) { 261 | mdb.mdb_stdout += chunk; 262 | mdb.doWork(); 263 | }); 264 | 265 | mdb.mdb_onprocexit = function (code) { 266 | if (code === 0) { 267 | throw (new Error('test exiting prematurely (' + 268 | 'mdb session not finalized)')); 269 | } 270 | }; 271 | process.on('exit', mdb.mdb_onprocexit); 272 | 273 | mdb.mdb_child.stderr.on('data', function (chunk) { 274 | mdb.mdb_stderr += chunk; 275 | mdb.doWork(); 276 | }); 277 | 278 | /* 279 | * The '1000$w' sets the terminal width to a large value to keep MDB 280 | * from inserting newlines at the default 80 columns. 281 | */ 282 | mdb.runCmd('1000$w\n', function (output, erroutput) { 283 | var cmdstr; 284 | if (!loaddmod) { 285 | callback(null, mdb); 286 | return; 287 | } 288 | 289 | assert.strictEqual(erroutput.length, 0); 290 | cmdstr = '::load ' + dmodpath() + '\n'; 291 | mdb.runCmd(cmdstr, function (loadoutput, loaderroutput) { 292 | assert.strictEqual(loaderroutput.length, 0, 293 | 'expected no stderr from ::load'); 294 | callback(null, mdb); 295 | }); 296 | }); 297 | 298 | return (mdb); 299 | } 300 | 301 | /* 302 | * Standalone test-cases do the following: 303 | * 304 | * - gcore the current process 305 | * - start up MDB on the current process 306 | * - invoke each of the specified functions as a vasync pipeline, with an 307 | * "MdbSession" as the sole initial argument 308 | * - on success, clean up the core file that was created 309 | */ 310 | function standaloneTest(funcs, callback) 311 | { 312 | var mdb; 313 | 314 | vasync.waterfall([ 315 | gcoreSelf, 316 | createMdbSessionFile, 317 | function runTestPipeline(mdbhdl, wfcallback) { 318 | mdb = mdbhdl; 319 | vasync.pipeline({ 320 | 'funcs': funcs, 321 | 'arg': mdbhdl 322 | }, wfcallback); 323 | } 324 | ], function (err) { 325 | if (!err) { 326 | mdb.finish(); 327 | callback(); 328 | return; 329 | } 330 | 331 | if (mdb) { 332 | err = new VError(err, 333 | 'test failed (keeping core file %s)', 334 | mdb.mdb_target_name); 335 | mdb.finish(err); 336 | } else { 337 | err = new VError(err, 'test failed'); 338 | } 339 | 340 | callback(err); 341 | }); 342 | } 343 | 344 | /* 345 | * This function should be invoked by callers of standaloneTest immediately 346 | * before invoking common.standaloneTest(). The argument should be a test 347 | * object that you will want to locate in the core file with findTestObject(). 348 | * 349 | * For context: the standalone tests generally use a single test object from 350 | * which other objects of interest may be referenced. In order to verify mdb_v8 351 | * functionality, these tests usually have to first locate this test object in 352 | * the core file. This is easiest if the object has a unique, well-known 353 | * property with a well-known value. This is a little cheesy, but we set this 354 | * property here to a boolean value. Because we're doing this immediately 355 | * prior to invoking gcoreSelf(), we minimize the chance that findTestObject() 356 | * finds multiple copies of the object created by intervening GC operations. 357 | */ 358 | function finalizeTestObject(obj) 359 | { 360 | obj['testObjectFinished'] = true; 361 | } 362 | 363 | /* 364 | * Uses the specified MDB session to locate our test object in the core file. 365 | * The test object is whatever object was nominated by a previous call to 366 | * finalizeTestObject(). 367 | */ 368 | function findTestObject(mdb, callback) 369 | { 370 | var cmdstr, rv; 371 | 372 | cmdstr = '::findjsobjects -p testObjectFinished | ' + 373 | '::findjsobjects | ' + 374 | '::jsprint -b testObjectFinished\n'; 375 | mdb.runCmd(cmdstr, function (output) { 376 | var lines, li, parts; 377 | 378 | lines = output.split('\n'); 379 | assert.strictEqual(lines[lines.length - 1].length, 0, 380 | 'last line was not empty'); 381 | 382 | for (li = 0; li < lines.length - 1; li++) { 383 | parts = lines[li].split(':'); 384 | if (parts.length == 2 && parts[1] == ' true') { 385 | if (rv !== undefined) { 386 | /* 387 | * We've probably found a garbage object 388 | * that's convincing enough that we 389 | * can't tell that it's wrong. 390 | */ 391 | callback(new Error( 392 | 'found more than one possible ' + 393 | 'test object')); 394 | return; 395 | } 396 | 397 | rv = parts[0]; 398 | } 399 | } 400 | 401 | if (rv === undefined) { 402 | callback(new Error('did not find test object')); 403 | } else { 404 | console.error('test object: ', rv); 405 | callback(null, rv); 406 | } 407 | }); 408 | } 409 | 410 | /* 411 | * Splits MDB output into lines, optionally verifying other properties. 412 | */ 413 | function splitMdbLines(output, options) 414 | { 415 | var lines; 416 | 417 | assert.equal('string', typeof (output)); 418 | assert.equal('object', typeof (options)); 419 | 420 | lines = output.split('\n'); 421 | assert.strictEqual(lines[lines.length - 1].length, 0, 422 | 'expected last line to be empty'); 423 | if (options.count !== undefined) { 424 | assert.equal('number', typeof (options.count)); 425 | assert.strictEqual(options.count, lines.length - 1); 426 | } 427 | 428 | return (lines.slice(0, lines.length - 1)); 429 | } 430 | -------------------------------------------------------------------------------- /test/standalone/gcore_self.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * test/standalone/gcore_self.js: utility function for creating a core file of 13 | * the current process. 14 | */ 15 | 16 | var assert = require('assert'); 17 | var childprocess = require('child_process'); 18 | var fs = require('fs'); 19 | var vasync = require('vasync'); 20 | var VError = require('verror'); 21 | 22 | /* Public interface */ 23 | module.exports = gcoreSelf; 24 | 25 | var DSCRIPT_FILE_SENTINEL = '/mdb_v8/sentinel'; 26 | var DSCRIPT_SENTINEL_BEGIN = 'mdb_v8: dtrace has started tracing'; 27 | var DSCRIPT_SENTINEL_DONE = 'mdb_v8: gcore has finished'; 28 | var DSCRIPT_SUFFIX = [ 29 | '/* This script was auto-generated by the mdb_v8 test suite. */', 30 | '', 31 | '#pragma D option destructive', 32 | '#pragma D option quiet', 33 | '', 34 | 'BEGIN', 35 | '{', 36 | ' printf("%s\\n", ' + JSON.stringify(DSCRIPT_SENTINEL_BEGIN) + ');', 37 | '}', 38 | '', 39 | 'syscall::stat:entry,', 40 | 'syscall::stat64:entry', 41 | '/pid == TARGET_PID && copyinstr(arg0) == ' + 42 | JSON.stringify(DSCRIPT_FILE_SENTINEL) + '/', 43 | '{', 44 | ' stop();', 45 | ' system("gcore -o %s %d && echo %s; prun %d", COREBASE, TARGET_PID,', 46 | ' ' + JSON.stringify(DSCRIPT_SENTINEL_DONE) + ', TARGET_PID);', 47 | ' exit(0);', 48 | '}' 49 | ].join('\n'); 50 | 51 | function makeDTraceArgs(corebase) 52 | { 53 | var script; 54 | 55 | script = [ 56 | '#define COREBASE ' + JSON.stringify(corebase), 57 | '#define TARGET_PID ' + process.pid, 58 | DSCRIPT_SUFFIX 59 | ].join('\n'); 60 | 61 | return ({ 62 | 'program': 'dtrace', 63 | 'script': script, 64 | 'argv': [ '-Cs', '/dev/stdin' ] 65 | }); 66 | } 67 | 68 | /* 69 | * Utility function for saving a core file of the current process using 70 | * gcore(1M). This is used in a number of tests as the basis for exercising 71 | * mdb_v8. This function invokes "callback" upon completion with arguments: 72 | * 73 | * - err, if there was any error 74 | * - filename, with the path to the specified file 75 | * 76 | * This implementation is a lot more complex than one might expect because it 77 | * attempts to ensure that the core file is taken when GC is not running. 78 | */ 79 | function gcoreSelf(callback) 80 | { 81 | var prefix, corefile, dtrace_args; 82 | var dtrace, state, error, buffered; 83 | 84 | prefix = '/var/tmp/node'; 85 | corefile = prefix + '.' + process.pid; 86 | dtrace_args = makeDTraceArgs(prefix); 87 | state = 'wait_for_trace'; 88 | error = null; 89 | 90 | console.error('gcoreSelf: begin'); 91 | dtrace = childprocess.spawn(dtrace_args.program, dtrace_args.argv); 92 | dtrace.stdin.end(dtrace_args.script); 93 | dtrace.stderr.on('data', function (data) { 94 | process.stderr.write('gcoreSelf: dtrace stderr: ' + data); 95 | }); 96 | 97 | buffered = ''; 98 | dtrace.stdout.on('data', function (data) { 99 | var sentinelExists; 100 | 101 | process.stderr.write('gcoreSelf: dtrace stdout: ' + data); 102 | buffered += data; 103 | 104 | if (state == 'wait_for_trace') { 105 | if (buffered.indexOf(DSCRIPT_SENTINEL_BEGIN) == -1) { 106 | /* Wait for the sentinel. */ 107 | console.error('gcoreSelf: waiting extra'); 108 | return; 109 | } 110 | 111 | /* 112 | * We have to stat the file synchronously in order to 113 | * know for sure that this thread will not be running 114 | * GC. We don't really care about the result, but we 115 | * check to be sure things haven't flown off the rails. 116 | */ 117 | console.error('gcoreSelf: dtrace is tracing'); 118 | state = 'wait_for_result'; 119 | try { 120 | fs.statSync(DSCRIPT_FILE_SENTINEL); 121 | sentinelExists = true; 122 | } catch (err) { 123 | assert.strictEqual(err.code, 'ENOENT'); 124 | sentinelExists = false; 125 | } 126 | assert.ok(sentinelExists === false, 127 | 'statSync of sentinel unexpectedly succeeded!'); 128 | 129 | /* 130 | * It shouldn't be possible to see both the initial 131 | * sentinel and the final sentinel on the same tick 132 | * because we must take action in between these. 133 | */ 134 | assert.strictEqual(-1, 135 | buffered.indexOf(DSCRIPT_SENTINEL_DONE)); 136 | return; 137 | } 138 | 139 | if (state == 'wait_for_result') { 140 | if (buffered.indexOf(DSCRIPT_SENTINEL_DONE) == -1) { 141 | /* Wait for the second sentinel. */ 142 | console.error('gcoreSelf: waiting extra'); 143 | return; 144 | } 145 | 146 | /* Success! */ 147 | state = 'succeeded'; 148 | console.error('gcoreSelf: gcore completed'); 149 | } 150 | }); 151 | 152 | dtrace.on('exit', function (code) { 153 | if (code !== 0) { 154 | error = new VError('dtrace exited unexpectedly with ' + 155 | 'code %s', code.toString()); 156 | } else if (state != 'succeeded') { 157 | error = new VError('dtrace exited successfully, but ' + 158 | 'never received sentinel'); 159 | } 160 | 161 | if (error) { 162 | callback(error); 163 | } else { 164 | console.error('gcoreSelf: gcore created %s', corefile); 165 | callback(null, corefile); 166 | } 167 | }); 168 | } 169 | -------------------------------------------------------------------------------- /test/standalone/tst.jsclosure.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | var assert = require('assert'); 12 | var childprocess = require('child_process'); 13 | var os = require('os'); 14 | var path = require('path'); 15 | var util = require('util'); 16 | 17 | var common = require('./common'); 18 | 19 | var getRuntimeVersions = require('../lib/runtime-versions').getRuntimeVersions; 20 | var compareV8Versions = require('../lib/runtime-versions').compareV8Versions; 21 | 22 | var gcoreSelf = require('./gcore_self'); 23 | 24 | var RUNTIME_VERSIONS = getRuntimeVersions(); 25 | var V8_VERSION = RUNTIME_VERSIONS.V8; 26 | var to, passed, mdb; 27 | var output = ''; 28 | var sentinel = 'SENTINEL\n'; 29 | 30 | /* 31 | * We're going to look for this function in ::jsfunctions. 32 | */ 33 | function doStuff(str) 34 | { 35 | var count = 10; 36 | function myClosure(elt, i) { 37 | /* 38 | * Do something with "str" and "count" so that we can see them 39 | * in ::jsclosure. 40 | */ 41 | if (str) { 42 | count--; 43 | } 44 | } 45 | 46 | [ 1 ].forEach(myClosure); 47 | to = setTimeout(myClosure, 30000); 48 | return (count); 49 | } 50 | 51 | var bindObject = { 52 | '__bind': doStuff.bind({ 'thisObj': true }, 53 | 'arg1value', 'arg2value', 'arg3value', 'arg4value') 54 | }; 55 | 56 | doStuff('hello world'); 57 | 58 | process.on('exit', function () { 59 | assert.ok(passed); 60 | console.error('test passed'); 61 | }); 62 | 63 | gcoreSelf(function onGcore(err, corefile) { 64 | var unlinkSync = require('fs').unlinkSync; 65 | var args = [ '-S', corefile ]; 66 | 67 | if (err) { 68 | console.error('failed to gcore self: %s', err.message); 69 | process.exit(1); 70 | } 71 | 72 | if (process.env.MDB_LIBRARY_PATH && process.env.MDB_LIBRARY_PATH != '') 73 | args = args.concat([ '-L', process.env.MDB_LIBRARY_PATH ]); 74 | 75 | mdb = childprocess.spawn('mdb', args, { stdio: 'pipe' }); 76 | 77 | mdb.on('exit', function (code2) { 78 | var retained = '; core retained as ' + corefile; 79 | 80 | if (code2 != 0) { 81 | console.error('mdb exited with code ' + 82 | util.inspect(code2) + retained); 83 | process.exit(code2); 84 | } 85 | 86 | unlinkSync(corefile); 87 | clearTimeout(to); 88 | /* process exit */ 89 | }); 90 | 91 | mdb.stdout.on('data', function (data) { 92 | output += data; 93 | while (output.indexOf(sentinel) != -1) 94 | doWork(); 95 | }); 96 | 97 | mdb.stderr.on('data', function (data) { 98 | console.log('mdb stderr: ' + data); 99 | }); 100 | 101 | /* 102 | * The '1000$w' sets the terminal width to a large value to keep MDB 103 | * from inserting newlines at the default 80 columns. 104 | */ 105 | var mod = util.format('1000$w; ::load %s\n', common.dmodpath()); 106 | doCmd(mod); 107 | }); 108 | 109 | function doWork() 110 | { 111 | var i, chunk; 112 | 113 | i = output.indexOf(sentinel); 114 | chunk = output.substr(0, i); 115 | output = output.substr(i + sentinel.length); 116 | console.error(chunk); 117 | processors[whichproc++](chunk); 118 | } 119 | 120 | function doCmd(str) 121 | { 122 | process.stderr.write('> ' + str); 123 | mdb.stdin.write(str); 124 | mdb.stdin.write('!echo ' + sentinel); 125 | } 126 | 127 | var processors, whichproc; 128 | var closure; 129 | var bindPtr; 130 | 131 | /* 132 | * This is effectively a pipeline of command response handlers. Each one kicks 133 | * off the next command. 134 | */ 135 | whichproc = 0; 136 | processors = [ 137 | function waitForModuleLoad() { 138 | doCmd('::jsfunctions -n myClosure ! awk \'NR == 2{ print $1 }\'\n'); 139 | }, 140 | 141 | function gotClosurePointer(chunk) { 142 | closure = chunk.trim(); 143 | assert.ok(closure.length > 0, 144 | 'did not find expected closure "myClosure"'); 145 | doCmd(util.format('%s::jsclosure\n', closure)); 146 | }, 147 | 148 | function gotClosureInfo(chunk) { 149 | var lines = chunk.split(/\n/); 150 | assert.equal(3, lines.length); 151 | /* JSSTYLED */ 152 | assert.ok(/^ "str": [a-z0-9]+: "hello world"/.test(lines[0])); 153 | /* JSSTYLED */ 154 | assert.ok(/^ "count": [a-z0-9]+: 9$/.test(lines[1])); 155 | assert.equal(lines[2], ''); 156 | 157 | doCmd(util.format('%s::v8function\n', closure)); 158 | }, 159 | 160 | function gotFunctionInfo(chunk) { 161 | var context; 162 | 163 | chunk.split(/\n/).forEach(function (line) { 164 | var parts = line.split(/\s+/); 165 | if (parts[0] == 'context:') 166 | context = parts[1]; 167 | }); 168 | 169 | assert.ok(context !== undefined); 170 | doCmd(util.format('%s::v8context\n', context)); 171 | }, 172 | 173 | function gotContext(chunk) { 174 | var lines = chunk.split(/\n/); 175 | var required = [ 176 | /* BEGIN JSSTYLED */ 177 | /* jsl:ignore */ 178 | /^closure function: [a-z0-9]+ \(JSFunction\)$/, 179 | /^previous context: [a-z0-9]+ \(FixedArray\)$/ 180 | /* jsl:end */ 181 | /* END JSSTYLED */ 182 | ]; 183 | 184 | /* 185 | * With V8 4.9.104 and later, the sentinel for context extension is "the 186 | * hole" instead of a SMI with value 0. See 187 | * https://codereview.chromium.org/1484723003. 188 | */ 189 | if (compareV8Versions(V8_VERSION, 190 | {major: 4, minor: 9, patch: 104}) >= 0) { 191 | /* BEGIN JSSTYLED */ 192 | /* jsl:ignore */ 193 | required.push(/^extension: [a-z0-9]+ \(Oddball: "hole"\)$/); 194 | /* jsl:end */ 195 | /* END JSSTYLED */ 196 | } else { 197 | required.push(/^extension: 0 \(SMI: value = 0\)$/); 198 | } 199 | 200 | /* 201 | * With V8 4.9.88 and later, the reference to the global object (an 202 | * instance of JSGlobalObject) was replaced in most cases by a reference 203 | * to the native context, which is an instance of a subclass of 204 | * FixedArray. 205 | * See https://codereview.chromium.org/1480003002. 206 | */ 207 | if (compareV8Versions(V8_VERSION, 208 | {major: 4, minor: 9, patch: 88}) >= 0) { 209 | required.push(/^native context: [a-z0-9]+ \(FixedArray\)$/); 210 | } else { 211 | required.push(/^global object: [a-z0-9]+ \(JSGlobalObject\)$/); 212 | } 213 | 214 | required = required.concat([ 215 | /* BEGIN JSSTYLED */ 216 | /* jsl:ignore */ 217 | /^ slot 0: [a-z0-9]+ \(.*\)$/, 218 | /^ slot 1: [a-z0-9]+ \(SMI: value = 9\)$/ 219 | /* jsl:end */ 220 | /* END JSSTYLED */ 221 | ]); 222 | 223 | var func; 224 | 225 | lines.forEach(function (line) { 226 | var i, parts; 227 | 228 | for (i = 0; i < required.length; i++) { 229 | if (required[i].test(line)) { 230 | required.splice(i, 1); 231 | break; 232 | } 233 | } 234 | 235 | if (/^closure function:/.test(line)) { 236 | parts = line.split(/\s+/); 237 | func = parts[2]; 238 | } 239 | }); 240 | 241 | assert.deepEqual([], required); 242 | doCmd(util.format('%s::v8function ! ' + 243 | 'awk \'/shared scope_info:/{ print $3 }\'\n', func)); 244 | }, 245 | 246 | function gotScopePtr(chunk) { 247 | var ptr = chunk.trim(); 248 | doCmd(util.format('%s::v8scopeinfo\n', ptr)); 249 | }, 250 | 251 | function gotScopeInfo(chunk) { 252 | var lines = chunk.split(/\n/); 253 | var required = [ 254 | /* BEGIN JSSTYLED */ 255 | /* jsl:ignore */ 256 | /^1 parameter$/, 257 | /^ parameter 0: [a-z0-9]+ \("str"\)$/, 258 | /^1 stack local variable$/, 259 | /^ stack local variable 0: [a-z0-9]+ \("myClosure"\)$/, 260 | /^2 context local variables$/, 261 | /^ context local variable 0: [a-z0-9]+ \("str"\)$/, 262 | /^ context local variable 1: [a-z0-9]+ \("count"\)$/ 263 | /* jsl:end */ 264 | /* END JSSTYLED */ 265 | ]; 266 | 267 | lines.forEach(function (line) { 268 | var i; 269 | for (i = 0; i < required.length; i++) { 270 | if (required[i].test(line)) { 271 | required.splice(i, 1); 272 | break; 273 | } 274 | } 275 | }); 276 | 277 | assert.deepEqual([], required); 278 | 279 | doCmd(util.format('::findjsobjects -p __bind | ::findjsobjects | ' + 280 | '::jsprint -ad1 __bind\n')); 281 | }, 282 | 283 | function gotBindCandidates(chunk) { 284 | var lines = chunk.split(/\n/); 285 | bindPtr = null; 286 | 287 | lines.forEach(function (l) { 288 | var parts = l.split(':'); 289 | if (bindPtr === null && parts.length == 2 && 290 | /function/.test(parts[1])) { 291 | bindPtr = parts[0]; 292 | } 293 | }); 294 | 295 | assert.notStrictEqual(null, bindPtr, 296 | 'did not find matching pointer'); 297 | 298 | doCmd(util.format('%s::jsfunction\n', bindPtr)); 299 | }, 300 | 301 | function gotJsFunction(chunk) { 302 | var lines, match, bindTarget, values, cmds; 303 | var i; 304 | 305 | lines = chunk.split(/\n/); 306 | if (lines.length != 7 || 307 | !/^bound function that wraps: [a-fA-F0-9]+/.test(lines[0]) || 308 | !/^with "this" = [a-fA-F0-9]+ \(.*Object\)/.test(lines[1]) || 309 | !/^ arg0 = [a-fA-F0-9]+ \(.*String\)/.test(lines[2]) || 310 | !/^ arg1 = [a-fA-F0-9]+ \(.*String\)/.test(lines[3]) || 311 | !/^ arg2 = [a-fA-F0-9]+ \(.*String\)/.test(lines[4]) || 312 | !/^ arg3 = [a-fA-F0-9]+ \(.*String\)/.test(lines[5])) { 313 | throw (new Error('::jsfunction output mismatch')); 314 | } 315 | 316 | match = lines[0].match(/^bound function that wraps: ([a-fA-F0-9]+)/); 317 | assert.notStrictEqual(match, null); 318 | bindTarget = match[1]; 319 | 320 | values = {}; 321 | for (i = 1; i < 6; i++) { 322 | match = lines[i].match( 323 | /* JSSTYLED */ 324 | /\s+"?(arg\d+|this)"?\s+= ([a-fA-F0-9]+)/); 325 | assert.notStrictEqual(null, match); 326 | values[match[1]] = match[2]; 327 | } 328 | 329 | console.error(values); 330 | cmds = [ bindTarget + '::jsfunction' ]; 331 | Object.keys(values).forEach(function (k) { 332 | cmds.push(values[k] + '::jsprint'); 333 | }); 334 | 335 | doCmd(cmds.join(';') + '\n'); 336 | mdb.stdin.end(); 337 | }, 338 | 339 | function gotJsFunctionValues(chunk) { 340 | var lines; 341 | 342 | lines = chunk.split(/\n/); 343 | assert.ok(/^defined at.*tst\.jsclosure\.js/.test(lines[1])); 344 | lines.splice(1, 1); 345 | 346 | assert.deepEqual(lines, [ 347 | 'function: doStuff', 348 | '{', 349 | ' "thisObj": true,', 350 | '}', 351 | '"arg1value"', 352 | '"arg2value"', 353 | '"arg3value"', 354 | '"arg4value"', 355 | '' 356 | ]); 357 | 358 | passed = true; 359 | } 360 | ]; 361 | -------------------------------------------------------------------------------- /test/standalone/tst.postmortem_basic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * tst.postmortem_basic.js: exercises code paths used to interpret as many 13 | * kinds of property values as we currently support. That includes all kinds of 14 | * strings, numbers, oddball values, arrays, objects, dates, regular 15 | * expressions, functions, and booleans. 16 | */ 17 | 18 | var common = require('./common'); 19 | 20 | var assert = require('assert'); 21 | 22 | /* 23 | * This class sets a variety of properties that together use most of the kinds 24 | * of values that currently support. As a result, using "findjsobjects" to 25 | * locate an instance of this class and then printing it out covers the code 26 | * responsible for interpreting at least some cases for each of these object 27 | * types. (For many values, there are many different cases, and it's hard to 28 | * ensure that we've covered all of them.) If the underlying V8 structures 29 | * change (or there's an mdb_v8 regression) and this test fails, it will likely 30 | * manifest as an object type that's not recognized any more (in which case 31 | * findjsobjects will consider it garbage and not list it by default) or the 32 | * "jsprint" output will be wrong. 33 | */ 34 | function Menagerie() { 35 | this.a_seqstring = 'my_string'; 36 | this.a_consstring = this.a_seqstring + '_suffix'; 37 | this.a_slicedstring = this.a_seqstring.slice(3, 5); 38 | 39 | this.a_number_smallint = 3; 40 | this.a_number_zero = 0; 41 | this.a_number_float = 4.7; 42 | this.a_number_large = Math.pow(2, 42); 43 | this.a_number_nan = NaN; 44 | 45 | this.a_null = null; 46 | this.a_undefined = undefined; 47 | 48 | this.a_array_empty = []; 49 | this.a_array_withstuff = [ 3, 5, null, this.a_seqstring ]; 50 | this.a_array_withhole = this.a_array_withstuff.slice(0); 51 | delete (this.a_array_withhole[1]); 52 | 53 | this.a_date_recent = new Date('2016-03-17'); 54 | this.a_date_zero = new Date(0); 55 | 56 | this.a_regexp = /animals walking upright/; 57 | this.a_func = function myFunc() {}; 58 | 59 | this.a_bool_true = true; 60 | this.a_bool_false = false; 61 | } 62 | 63 | 64 | /* 65 | * This class is used to exercise a special case of double-precision 66 | * floating-point values with double unboxing enabled. The goal is to have an 67 | * object with double-precision values beyond property 31. We want to make sure 68 | * there are other non-double to make sure our bitwise logic is correct when 69 | * examining the layout descriptor. See the comments in mdb_v8.c around unboxed 70 | * double values for details. 71 | * 72 | * Note that we cannot use a loop here because V8 will transform this into an 73 | * object with dictionary properties. 74 | */ 75 | function Zoo() 76 | { 77 | this.prop_00 = 'value_00'; 78 | this.prop_01 = 'value_01'; 79 | this.prop_02 = 'value_02'; 80 | this.prop_03 = 'value_03'; 81 | this.prop_04 = 'value_04'; 82 | this.prop_05 = 'value_05'; 83 | this.prop_06 = 'value_06'; 84 | this.prop_07 = 'value_07'; 85 | this.prop_08 = 'value_08'; 86 | this.prop_09 = 'value_09'; 87 | this.prop_10 = 'value_10'; 88 | this.prop_11 = 11.1111111; 89 | this.prop_12 = 'value_12'; 90 | this.prop_13 = 'value_13'; 91 | this.prop_14 = 'value_14'; 92 | this.prop_15 = 'value_15'; 93 | this.prop_16 = 'value_16'; 94 | this.prop_17 = 17.1717171; 95 | this.prop_18 = 'value_18'; 96 | this.prop_19 = 'value_19'; 97 | this.prop_20 = 'value_20'; 98 | this.prop_21 = 'value_21'; 99 | this.prop_22 = 'value_22'; 100 | this.prop_23 = 'value_23'; 101 | this.prop_24 = 'value_24'; 102 | this.prop_25 = 'value_25'; 103 | this.prop_26 = 'value_26'; 104 | this.prop_27 = 'value_27'; 105 | this.prop_28 = 'value_28'; 106 | this.prop_29 = 'value_29'; 107 | this.prop_30 = 'value_30'; 108 | this.prop_31 = 'value_31'; 109 | this.prop_32 = 'value_32'; 110 | this.prop_33 = 33.3333333; 111 | this.prop_34 = 'value_34'; 112 | this.prop_35 = 'value_35'; 113 | this.prop_36 = 'value_36'; 114 | } 115 | 116 | var obj1 = new Menagerie(); 117 | var obj2 = new Zoo(); 118 | 119 | common.standaloneTest([ 120 | function testMenagerie(mdb, callback) { 121 | mdb.runCmd('::findjsobjects -c Menagerie | ' + 122 | '::findjsobjects -p a_seqstring |' + 123 | '::findjsobjects | ::jsprint\n', function (output) { 124 | assert.equal([ 125 | '{', 126 | ' "a_seqstring": "my_string",', 127 | ' "a_consstring": "my_string_suffix",', 128 | ' "a_slicedstring": "st",', 129 | ' "a_number_smallint": 3,', 130 | ' "a_number_zero": 0,', 131 | ' "a_number_float": 4.700000e+00,', 132 | ' "a_number_large": 4398046511104,', 133 | ' "a_number_nan": NaN,', 134 | ' "a_null": null,', 135 | ' "a_undefined": undefined,', 136 | ' "a_array_empty": [],', 137 | ' "a_array_withstuff": [', 138 | ' 3,', 139 | ' 5,', 140 | ' null,', 141 | ' "my_string",', 142 | ' ],', 143 | ' "a_array_withhole": [', 144 | ' 3,', 145 | ' hole,', 146 | ' null,', 147 | ' "my_string",', 148 | ' ],', 149 | ' "a_date_recent": 1458172800000 ' + 150 | '(2016 Mar 17 00:00:00),', 151 | ' "a_date_zero": 0 (1970 Jan 1 00:00:00),', 152 | ' "a_regexp": JSRegExp: "animals walking upright",', 153 | ' "a_bool_true": true,', 154 | ' "a_bool_false": false,', 155 | '}', 156 | '' 157 | ].join('\n'), output); 158 | callback(); 159 | }); 160 | }, 161 | 162 | function testZoo(mdb, callback) { 163 | mdb.runCmd('::findjsobjects -c Zoo | ' + 164 | '::findjsobjects -p prop_01 |' + 165 | '::findjsobjects | ::jsprint\n', function (output) { 166 | assert.equal([ 167 | '{', 168 | ' "prop_00": "value_00",', 169 | ' "prop_01": "value_01",', 170 | ' "prop_02": "value_02",', 171 | ' "prop_03": "value_03",', 172 | ' "prop_04": "value_04",', 173 | ' "prop_05": "value_05",', 174 | ' "prop_06": "value_06",', 175 | ' "prop_07": "value_07",', 176 | ' "prop_08": "value_08",', 177 | ' "prop_09": "value_09",', 178 | ' "prop_10": "value_10",', 179 | ' "prop_11": 1.111111e+01,', 180 | ' "prop_12": "value_12",', 181 | ' "prop_13": "value_13",', 182 | ' "prop_14": "value_14",', 183 | ' "prop_15": "value_15",', 184 | ' "prop_16": "value_16",', 185 | ' "prop_17": 1.717172e+01,', 186 | ' "prop_18": "value_18",', 187 | ' "prop_19": "value_19",', 188 | ' "prop_20": "value_20",', 189 | ' "prop_21": "value_21",', 190 | ' "prop_22": "value_22",', 191 | ' "prop_23": "value_23",', 192 | ' "prop_24": "value_24",', 193 | ' "prop_25": "value_25",', 194 | ' "prop_26": "value_26",', 195 | ' "prop_27": "value_27",', 196 | ' "prop_28": "value_28",', 197 | ' "prop_29": "value_29",', 198 | ' "prop_30": "value_30",', 199 | ' "prop_31": "value_31",', 200 | ' "prop_32": "value_32",', 201 | ' "prop_33": 3.333333e+01,', 202 | ' "prop_34": "value_34",', 203 | ' "prop_35": "value_35",', 204 | ' "prop_36": "value_36",', 205 | '}', 206 | '' 207 | ].join('\n'), output); 208 | callback(); 209 | }); 210 | } 211 | ], function (err) { 212 | if (err) { 213 | throw (err); 214 | } 215 | 216 | console.log('%s passed', process.argv[1]); 217 | }); 218 | -------------------------------------------------------------------------------- /test/standalone/tst.postmortem_details.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | 12 | var assert = require('assert'); 13 | var childprocess = require('child_process'); 14 | var os = require('os'); 15 | var path = require('path'); 16 | var util = require('util'); 17 | 18 | var bufferCommands = require('../lib/buffer-commands'); 19 | var common = require('./common'); 20 | var getRuntimeVersions = require('../lib/runtime-versions').getRuntimeVersions; 21 | 22 | var RUNTIME_VERSIONS = getRuntimeVersions(); 23 | var V8_VERSION = RUNTIME_VERSIONS.V8; 24 | var NODE_VERSION = RUNTIME_VERSIONS.node; 25 | 26 | var gcoreSelf = require('./gcore_self'); 27 | 28 | /* 29 | * We're going to look specifically for this function and buffer in the core 30 | * file. 31 | */ 32 | function myTestFunction() 33 | { 34 | [ 1 ].forEach(function myIterFunction(t) {}); 35 | return (new Buffer(bufstr)); 36 | } 37 | 38 | var bufstr, mybuffer, slicedBuffer, slicedBufferLength; 39 | 40 | /* 41 | * Run myTestFunction() three times to create multiple instances of 42 | * myIterFunction. 43 | */ 44 | bufstr = 'Hello, test suite!'; 45 | mybuffer = myTestFunction(bufstr); 46 | mybuffer = myTestFunction(bufstr); 47 | mybuffer = myTestFunction(bufstr); 48 | mybuffer.my_buffer = true; 49 | 50 | slicedBufferLength = 5; 51 | slicedBuffer = mybuffer.slice(0, slicedBufferLength); 52 | slicedBuffer.is_sliced_buffer = true; 53 | 54 | var OBJECT_KINDS = ['dict', 'inobject', 'numeric', 'props']; 55 | 56 | /* 57 | * Now we're going to fork ourselves to gcore 58 | */ 59 | var tmpfile = '/var/tmp/node-postmortem-func' + '.' + process.pid; 60 | var output = ''; 61 | 62 | gcoreSelf(function (err, corefile) { 63 | var unlinkSync = require('fs').unlinkSync; 64 | var args = [ '-S', corefile ]; 65 | 66 | if (err) { 67 | console.error('failed to gcore self: %s', err.message); 68 | process.exit(1); 69 | } 70 | 71 | if (process.env.MDB_LIBRARY_PATH && process.env.MDB_LIBRARY_PATH != '') 72 | args = args.concat([ '-L', process.env.MDB_LIBRARY_PATH ]); 73 | 74 | var mdb = childprocess.spawn('mdb', args, { stdio: 'pipe' }); 75 | 76 | mdb.on('exit', function (code2) { 77 | unlinkSync(tmpfile); 78 | var retained = '; core retained as ' + corefile; 79 | 80 | if (code2 != 0) { 81 | console.error('mdb exited with code ' + 82 | util.inspect(code2) + retained); 83 | process.exit(code2); 84 | } 85 | 86 | var lines = output.split(/\n/); 87 | var current = null, testname = null; 88 | var whichtest = -1; 89 | var i; 90 | 91 | for (i = 0; i < lines.length; i++) { 92 | if (lines[i].indexOf('test: ') === 0) { 93 | if (current !== null) { 94 | console.error('verifying ' + testname + 95 | ' using ' + 96 | verifiers[whichtest].name); 97 | verifiers[whichtest](current); 98 | } 99 | whichtest++; 100 | current = []; 101 | testname = lines[i]; 102 | continue; 103 | } 104 | 105 | if (current !== null) 106 | current.push(lines[i]); 107 | } 108 | 109 | console.error('verifying ' + testname + ' using ' + 110 | verifiers[whichtest].name); 111 | verifiers[whichtest](current); 112 | 113 | unlinkSync(corefile); 114 | process.exit(0); 115 | }); 116 | 117 | mdb.stdout.on('data', function (data) { 118 | output += data; 119 | }); 120 | 121 | mdb.stderr.on('data', function (data) { 122 | console.log('mdb stderr: ' + data); 123 | }); 124 | 125 | var verifiers = []; 126 | var bufferAddress, slicedBufferAddress; 127 | verifiers.push(function verifyConstructor(testlines) { 128 | if (NODE_VERSION.major < 4) { 129 | assert.deepEqual(testlines, [ 'Buffer' ]); 130 | } else { 131 | assert.deepEqual(testlines, [ 'Uint8Array' ]); 132 | } 133 | }); 134 | verifiers.push(function verifyNodebuffer(testlines) { 135 | assert.equal(testlines.length, 1); 136 | assert.ok(/^[0-9a-fA-F]+$/.test(testlines[0])); 137 | bufferAddress = testlines[0]; 138 | }); 139 | verifiers.push(function verifyBufferContents(testlines) { 140 | assert.equal(testlines.length, 1); 141 | assert.equal(testlines[0], '0x' + bufferAddress + 142 | ': Hello'); 143 | }); 144 | // Buffer instances are implemented as typed arrays in Node 145 | // versions that ship with V8 >= 4.6. Typed arrays in these versions 146 | // of V8 do not *directly* store their underlying buffer as an 147 | // "internal" element, so ::v8internal would not output its address. 148 | // It would instead output the address of a FixedTypedArrayBase 149 | // instance. Thus, skip the test. 150 | if (V8_VERSION.major < 4 || V8_VERSION.major === 4 && 151 | V8_VERSION.minor < 6) { 152 | verifiers.push(function verifyV8internal(testlines) { 153 | assert.deepEqual(testlines, [ bufferAddress ]); 154 | }); 155 | } 156 | verifiers.push(function verifySlicedBufferConstructor(testlines) { 157 | if (NODE_VERSION.major >= 4) { 158 | assert.deepEqual(testlines, [ 'Uint8Array' ]); 159 | } else if (NODE_VERSION.major === 0 && 160 | NODE_VERSION.minor === 12) { 161 | assert.deepEqual(testlines, [ 'NativeBuffer' ]); 162 | } else { 163 | assert.deepEqual(testlines, [ 'Buffer' ]); 164 | } 165 | }); 166 | verifiers.push(function verifySlicedNodebuffer(testlines) { 167 | assert.equal(testlines.length, 1); 168 | assert.ok(/^[0-9a-fA-F]+$/.test(testlines[0])); 169 | slicedBufferAddress = testlines[0]; 170 | }); 171 | verifiers.push(function verifySlicedBufferContents(testlines) { 172 | assert.equal(testlines.length, 1); 173 | assert.equal(testlines[0], '0x' + slicedBufferAddress + 174 | ': Hello'); 175 | }); 176 | 177 | // Buffer instances are implemented as typed arrays in Node 178 | // versions that ship with V8 >= 4.6. Typed arrays in these versions 179 | // of V8 do not *directly* store their underlying buffer as an 180 | // "internal" element, so ::v8internal would not output its address. 181 | // It would instead output the address of a FixedTypedArrayBase 182 | // instance. Thus, skip the test. 183 | if (V8_VERSION.major < 4 || V8_VERSION.major === 4 && 184 | V8_VERSION.minor < 6) { 185 | verifiers.push(function verifySliceBufferV8internal(testlines) { 186 | assert.deepEqual(testlines, [ slicedBufferAddress ]); 187 | }); 188 | } 189 | 190 | verifiers.push(function verifyJsfunctionN(testlines) { 191 | assert.equal(testlines.length, 2); 192 | var parts = testlines[1].trim().split(/\s+/); 193 | assert.equal(parts[1], 1); 194 | assert.equal(parts[2], 'myTestFunction'); 195 | assert.ok(parts[3].indexOf('tst.postmortem_details.js') != -1); 196 | }); 197 | verifiers.push(function verifyJsfunctionS(testlines) { 198 | var foundtest = false, founditer = false; 199 | assert.ok(testlines.length > 1); 200 | testlines.forEach(function (line) { 201 | var parts = line.trim().split(/\s+/); 202 | if (parts[2] == 'myIterFunction') { 203 | assert.equal(parts[1], '3'); 204 | founditer = true; 205 | } else if (parts[2] == 'myTestFunction') { 206 | foundtest = true; 207 | assert.equal(parts[1], '1'); 208 | } 209 | }); 210 | assert.ok(foundtest); 211 | assert.ok(founditer); 212 | }); 213 | verifiers.push(function verifyJssource(testlines) { 214 | var content = testlines.join('\n'); 215 | assert.ok(testlines[0].indexOf('tst.postmortem_details.js') 216 | != -1); 217 | assert.ok(content.indexOf('function myTestFunction()\n') 218 | != -1); 219 | assert.ok(content.indexOf('return (new Buffer(bufstr));\n') 220 | != -1); 221 | }); 222 | OBJECT_KINDS.forEach(function (kind) { 223 | verifiers.push(function verifyFindObjectsKind(testLines) { 224 | // There should be at least one object for 225 | // every kind of objects (except for the special cases 226 | // below) 227 | var expectedMinimumObjs = 1; 228 | 229 | if (kind === 'props') { 230 | // On versions > 0.10.x, currently there's no 231 | // object with the kind 'props'. There should 232 | // be, but it's a minor issue we're or to live 233 | // with for now. 234 | expectedMinimumObjs = 0; 235 | } 236 | 237 | assert.ok(testLines.length >= expectedMinimumObjs); 238 | }); 239 | }); 240 | 241 | var mod = util.format('::load %s\n', common.dmodpath()); 242 | mdb.stdin.write(mod); 243 | mdb.stdin.write('!echo test: jsconstructor\n'); 244 | mdb.stdin.write(bufferCommands.getFindBufferAddressCmd({ 245 | propertyName: 'my_buffer', 246 | length: bufstr.length, 247 | outputFile: tmpfile 248 | })); 249 | mdb.stdin.write('::cat ' + tmpfile + ' | ::jsconstructor\n'); 250 | mdb.stdin.write('!echo test: nodebuffer\n'); 251 | mdb.stdin.write('::cat ' + tmpfile + ' | ::nodebuffer\n'); 252 | mdb.stdin.write('!echo test: nodebuffer contents\n'); 253 | mdb.stdin.write('::cat ' + tmpfile + 254 | ' | ::nodebuffer | ::eval "./ccccc"\n'); 255 | 256 | // Buffer instances are implemented as typed arrays in Node 257 | // versions that ship with V8 >= 4.6. Typed arrays in these versions 258 | // of V8 do not *directly* store their underlying buffer as an 259 | // "internal" element, so ::v8internal would not output its address. 260 | // It would instead output the address of a FixedTypedArrayBase 261 | // instance. Thus, skip the test. 262 | if (V8_VERSION.major < 4 || V8_VERSION.major === 4 && 263 | V8_VERSION.minor < 6) { 264 | mdb.stdin.write('!echo test: v8internal\n'); 265 | mdb.stdin.write('::cat ' + tmpfile + 266 | ' | ::v8print ! awk \'$2 == "elements"{' + 267 | 'print $4 }\' > ' + tmpfile + '\n'); 268 | mdb.stdin.write('::cat ' + tmpfile + ' | ::v8internal 0\n'); 269 | } 270 | // Tests that sliced buffers can be inspected properly. See related 271 | // issue: https://github.com/joyent/mdb_v8/issues/58. 272 | mdb.stdin.write('!echo test: sliced buffer\n'); 273 | mdb.stdin.write(bufferCommands.getFindBufferAddressCmd({ 274 | propertyName: 'is_sliced_buffer', 275 | length: slicedBufferLength, 276 | outputFile: tmpfile 277 | })); 278 | mdb.stdin.write('::cat ' + tmpfile + ' | ::jsconstructor\n'); 279 | mdb.stdin.write('!echo test: sliced nodebuffer\n'); 280 | mdb.stdin.write('::cat ' + tmpfile + ' | ::nodebuffer\n'); 281 | mdb.stdin.write('!echo test: sliced nodebuffer contents\n'); 282 | mdb.stdin.write('::cat ' + tmpfile + 283 | ' | ::nodebuffer | ::eval "./ccccc"\n'); 284 | 285 | // Buffer instances are implemented as typed arrays in Node 286 | // versions that ship with V8 >= 4.6. Typed arrays in these versions 287 | // of V8 do not *directly* store their underlying buffer as an 288 | // "internal" element, so ::v8internal would not output its address. 289 | // It would instead output the address of a FixedTypedArrayBase 290 | // instance. Thus, skip the test. 291 | if (V8_VERSION.major < 4 || V8_VERSION.major === 4 && 292 | V8_VERSION.minor < 6) { 293 | mdb.stdin.write('!echo test: v8internal\n'); 294 | mdb.stdin.write('::cat ' + tmpfile + 295 | ' | ::v8print ! awk \'$2 == "elements"{' + 296 | 'print $4 }\' > ' + tmpfile + '\n'); 297 | mdb.stdin.write('::cat ' + tmpfile + ' | ::v8internal 0\n'); 298 | } 299 | 300 | mdb.stdin.write('!echo test: jsfunctions -n\n'); 301 | mdb.stdin.write('::jsfunctions -n myTestFunction ! cat\n'); 302 | mdb.stdin.write('!echo test: jsfunctions -s\n'); 303 | mdb.stdin.write('::jsfunctions -s tst.postmortem_details.js ! cat\n'); 304 | mdb.stdin.write('!echo test: jssource\n'); 305 | mdb.stdin.write('::jsfunctions -n myTestFunction ! ' + 306 | 'awk \'NR == 2 {print $1}\' | head -1 > ' + tmpfile + '\n'); 307 | mdb.stdin.write('::cat ' + tmpfile + ' | ::jssource -n 0\n'); 308 | OBJECT_KINDS.forEach(function (kind) { 309 | mdb.stdin.write(util.format( 310 | '!echo test: findjsobjects -k %s\n', kind)); 311 | mdb.stdin.write(util.format('::findjsobjects -k %s\n', kind)); 312 | }); 313 | mdb.stdin.end(); 314 | }); 315 | -------------------------------------------------------------------------------- /test/standalone/tst.postmortem_findjsobjects.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2015, Joyent, Inc. 9 | */ 10 | 11 | var common = require('./common'); 12 | var assert = require('assert'); 13 | var os = require('os'); 14 | var path = require('path'); 15 | var util = require('util'); 16 | 17 | function Foo() {} 18 | function LanguageH(chapter) { 19 | this.OBEY = 'CHAPTER ' + parseInt(chapter, 10); 20 | // This reference is used for the ::findjsobjects -r test below 21 | this.foo = new Foo(); 22 | } 23 | 24 | var obj = new LanguageH(1); 25 | 26 | /* 27 | * Now we're going to fork ourselves to gcore 28 | */ 29 | var spawn = require('child_process').spawn; 30 | var prefix = '/var/tmp/node'; 31 | var corefile = prefix + '.' + process.pid; 32 | var gcore = spawn('gcore', [ '-o', prefix, process.pid + '' ]); 33 | var output = ''; 34 | var unlinkSync = require('fs').unlinkSync; 35 | var args = [ '-S', corefile ]; 36 | 37 | if (process.env.MDB_LIBRARY_PATH && process.env.MDB_LIBRARY_PATH != '') 38 | args = args.concat([ '-L', process.env.MDB_LIBRARY_PATH ]); 39 | 40 | gcore.stderr.on('data', function (data) { 41 | console.log('gcore: ' + data); 42 | }); 43 | 44 | function verifyTest(testName, verifiers, verifierIndex, testOutput) { 45 | assert.ok(typeof (testName) === 'string', 46 | 'testName must be a string'); 47 | assert.ok(Array.isArray(verifiers), 'verifiers must be an array'); 48 | assert.ok(typeof (verifierIndex) === 'number' && 49 | isFinite(verifierIndex), 50 | 'verifierIndex must be a finite number'); 51 | assert.ok(Array.isArray(testOutput), 52 | 'testOutput must be an array'); 53 | 54 | var verifier = verifiers[verifierIndex]; 55 | assert.ok(verifier, 'verifier for test ' + testName 56 | + ' must exists'); 57 | console.error('verifying ' + testName + ' using ' 58 | + verifier.name); 59 | verifier(testOutput); 60 | } 61 | 62 | gcore.on('exit', function (code) { 63 | var verifiers = []; 64 | 65 | if (code != 0) { 66 | console.error('gcore exited with code ' + code); 67 | process.exit(code); 68 | } 69 | 70 | var mdb = spawn('mdb', args, { stdio: 'pipe' }); 71 | 72 | mdb.on('exit', function (code2) { 73 | var verifierIndex = -1; 74 | var testName; 75 | var currentTestOutput = null; 76 | var i; 77 | 78 | var retained = '; core retained as ' + corefile; 79 | 80 | if (code2 != 0) { 81 | console.error('mdb exited with code ' + 82 | util.inspect(code2) + retained); 83 | process.exit(code2); 84 | } 85 | 86 | var lines = output.split('\n'); 87 | 88 | for (i = 0; i < lines.length; i++) { 89 | // Found a new test 90 | if (lines[i].indexOf('test: ') === 0) { 91 | // If we already were parsing a previous test, 92 | // run the verifier for this previous test. 93 | if (currentTestOutput !== null) { 94 | verifyTest(testName, verifiers, 95 | verifierIndex, currentTestOutput); 96 | } 97 | 98 | // Move to the next verifier function and reset 99 | // the test output and name for this new test. 100 | ++verifierIndex; 101 | currentTestOutput = []; 102 | testName = lines[i]; 103 | continue; 104 | } 105 | 106 | // Accumulate test output for the current test. 107 | if (currentTestOutput !== null) { 108 | currentTestOutput.push(lines[i]); 109 | } 110 | } 111 | 112 | // Verify the last test 113 | verifyTest(testName, verifiers, verifierIndex, 114 | currentTestOutput); 115 | 116 | unlinkSync(corefile); 117 | process.exit(0); 118 | }); 119 | 120 | mdb.stdout.on('data', function (data) { 121 | output += data; 122 | }); 123 | 124 | mdb.stderr.on('data', function (data) { 125 | console.log('mdb stderr: ' + data); 126 | }); 127 | 128 | verifiers.push(function verifyFindjsobjectsByConstructor(cmdOutput) { 129 | var expectedOutputLine = '"OBEY": "' + obj.OBEY + '"'; 130 | assert.ok(cmdOutput.some(function findExpectedLine(line) { 131 | return (line.indexOf(expectedOutputLine) !== -1); 132 | })); 133 | }); 134 | 135 | verifiers.push(function verifyFindjsobjectsByProperty(cmdOutput) { 136 | var expectedOutputLine = '"OBEY": "' + obj.OBEY + '"'; 137 | assert.ok(cmdOutput.some(function findExpectedLine(line) { 138 | return (line.indexOf(expectedOutputLine) !== -1); 139 | })); 140 | }); 141 | 142 | verifiers.push(function verifyFindjsobjectsByReference(cmdOutput) { 143 | var refRegexp = 144 | /^[0-9a-fA-F]+ referred to by\s([0-9a-fA-F]+).foo/; 145 | assert.ok(cmdOutput.length > 0, 146 | '::findjsobjects -r should output at least one line'); 147 | 148 | // Finding just one line that outputs a reference from .foo is 149 | // enough, since ::findjsobjects -c Foo | ::findjsobjects could 150 | // output addresses that do not represent the actual instance 151 | // referenced by the .foo property. 152 | assert.ok(cmdOutput.some(function findReferenceOutput(line) { 153 | return (line.match(refRegexp) !== null); 154 | }), '::findjsobjects -r output should match ' + refRegexp); 155 | }); 156 | 157 | var mod = util.format('::load %s\n', common.dmodpath()); 158 | mdb.stdin.write(mod); 159 | 160 | mdb.stdin.write('!echo test: findjsobjects by constructor\n'); 161 | mdb.stdin.write('::findjsobjects -c LanguageH | '); 162 | mdb.stdin.write('::findjsobjects | ::jsprint\n'); 163 | 164 | mdb.stdin.write('!echo test: findjsobjects by property\n'); 165 | mdb.stdin.write('::findjsobjects -p OBEY | '); 166 | mdb.stdin.write('::findjsobjects | ::jsprint\n'); 167 | 168 | mdb.stdin.write('!echo test: findjsobjects by reference\n'); 169 | mdb.stdin.write('::findjsobjects -c Foo | ::findjsobjects'); 170 | mdb.stdin.write('| ::findjsobjects -r\n'); 171 | 172 | mdb.stdin.end(); 173 | }); 174 | -------------------------------------------------------------------------------- /test/standalone/tst.postmortem_jsstack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2017, Joyent, Inc. 9 | */ 10 | 11 | var common = require('./common'); 12 | var assert = require('assert'); 13 | var os = require('os'); 14 | var path = require('path'); 15 | var util = require('util'); 16 | 17 | var getRuntimeVersions = require('../lib/runtime-versions').getRuntimeVersions; 18 | 19 | var RUNTIME_VERSIONS = getRuntimeVersions(); 20 | var V8_VERSION = RUNTIME_VERSIONS.V8; 21 | 22 | /* 23 | * Some functions to create a recognizable stack. 24 | */ 25 | var frames = [ 'stalloogle', 'bagnoogle', 'doogle' ]; 26 | var expected; 27 | 28 | var stalloogle = function (str) { 29 | expected = str; 30 | os.loadavg(); 31 | }; 32 | 33 | var bagnoogle = function (arg0, arg1) { 34 | stalloogle(arg0 + ' is ' + arg1 + ' except that it is read-only'); 35 | }; 36 | 37 | var done = false; 38 | 39 | var doogle = function () { 40 | if (!done) 41 | setTimeout(doogle, 10); 42 | 43 | bagnoogle('The bfs command', '(almost) like ed(1)'); 44 | }; 45 | 46 | var spawn = require('child_process').spawn; 47 | var prefix = '/var/tmp/node'; 48 | var corefile = prefix + '.' + process.pid; 49 | var args = [ '-S', corefile ]; 50 | 51 | if (process.env.MDB_LIBRARY_PATH && process.env.MDB_LIBRARY_PATH != '') 52 | args = args.concat([ '-L', process.env.MDB_LIBRARY_PATH ]); 53 | 54 | /* 55 | * We're going to use DTrace to stop us, gcore us, and set us running again 56 | * when we call getloadavg() -- with the implicit assumption that our 57 | * deepest function is the only caller of os.loadavg(). 58 | */ 59 | var dtrace = spawn('dtrace', [ '-qwn', 'syscall::getloadavg:entry/pid == ' + 60 | process.pid + '/{stop(); system("gcore -o ' + 61 | prefix + ' %d", pid); system("prun %d", pid); exit(0); }' ]); 62 | 63 | var output = ''; 64 | var unlinkSync = require('fs').unlinkSync; 65 | 66 | dtrace.stderr.on('data', function (data) { 67 | console.log('dtrace: ' + data); 68 | }); 69 | 70 | dtrace.on('exit', function (code) { 71 | if (code != 0) { 72 | console.error('dtrace exited with code ' + code); 73 | process.exit(code); 74 | } 75 | 76 | done = true; 77 | 78 | /* 79 | * We have our core file. Now we need to fire up mdb to analyze it... 80 | */ 81 | var mdb = spawn('mdb', args, { stdio: 'pipe' }); 82 | 83 | var mod = util.format('::load %s\n', common.dmodpath()); 84 | mdb.on('exit', function (code2) { 85 | var retained = '; core retained as ' + corefile; 86 | 87 | if (code2 != 0) { 88 | console.error('mdb exited with code ' + code2 + 89 | retained); 90 | process.exit(code2); 91 | } 92 | 93 | var sentinel = 'js: '; 94 | /* 95 | * Starting with https://codereview.chromium.org/1749353004 and 96 | * V8 5.1.39, function definitions of the following form: 97 | * 98 | * var foo = function () {} 99 | * 100 | * don't only have an inferred name, but also an actual name. 101 | * As a result, their representation by mdb_v8's ::jsstack 102 | * command doesn't include the " as " prefix. 103 | */ 104 | if (V8_VERSION.major < 5 || 105 | (V8_VERSION.major === 5 && V8_VERSION.minor < 1)) { 106 | sentinel += ' (as '; 107 | } 108 | 109 | var arg1 = ' arg1: '; 110 | var lines = output.split('\n'); 111 | var matched = 0; 112 | var straddr = undefined; 113 | 114 | for (var i = 0; i < lines.length; i++) { 115 | var line = lines[i]; 116 | 117 | /* 118 | * Some later versions of node, beginning with v6, have 119 | * an additional JS stack frame for os.loadavg(). 120 | * Ignore this frame. 121 | */ 122 | if ((line.indexOf(sentinel) !== -1) && 123 | (line.indexOf('loadavg') !== -1)) { 124 | continue; 125 | } 126 | 127 | if (matched == 1 && line.indexOf(arg1) === 0) { 128 | straddr = 129 | line.substr(arg1.length).split(' ')[0]; 130 | } 131 | 132 | if (line.indexOf(sentinel) == -1 || 133 | frames.length === 0) 134 | continue; 135 | 136 | var frame = line.substr(line.indexOf(sentinel) + 137 | sentinel.length); 138 | var top = frames.shift(); 139 | 140 | assert.equal(frame.indexOf(top), 0, 141 | 'unexpected frame where ' + 142 | top + ' was expected' + retained); 143 | 144 | matched++; 145 | } 146 | 147 | assert.equal(frames.length, 0, 'did not find expected frame ' + 148 | frames[0] + retained); 149 | 150 | assert.notEqual(straddr, undefined, 151 | 'did not find arg1 for top frame' + retained); 152 | 153 | /* 154 | * Now we're going to take one more swing at the core file to 155 | * print out the argument string that we found. 156 | */ 157 | output = ''; 158 | mdb = spawn('mdb', args, { stdio: 'pipe' }); 159 | 160 | mdb.on('exit', function (code3) { 161 | if (code3 != 0) { 162 | console.error('mdb (second) exited with code3 ' 163 | + code3 + retained); 164 | process.exit(code3); 165 | } 166 | 167 | assert.notEqual(output.indexOf(expected), -1, 168 | 'did not find arg1 (' + straddr + 169 | ') to contain expected string' + retained); 170 | 171 | unlinkSync(corefile); 172 | process.exit(0); 173 | }); 174 | 175 | mdb.stdout.on('data', function (data) { 176 | output += data; 177 | }); 178 | 179 | mdb.stderr.on('data', function (data) { 180 | console.log('mdb (second) stderr: ' + data); 181 | }); 182 | 183 | mdb.stdin.write(mod); 184 | mdb.stdin.write(straddr + '::v8str\n'); 185 | mdb.stdin.end(); 186 | }); 187 | 188 | mdb.stdout.on('data', function (data) { 189 | output += data; 190 | }); 191 | 192 | mdb.stderr.on('data', function (data) { 193 | console.log('mdb stderr: ' + data); 194 | }); 195 | 196 | mdb.stdin.write(mod); 197 | mdb.stdin.write('::jsstack -v\n'); 198 | mdb.stdin.end(); 199 | }); 200 | 201 | setTimeout(doogle, 10); 202 | -------------------------------------------------------------------------------- /test/standalone/tst.v8whatis.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | /* 8 | * Copyright (c) 2018, Joyent, Inc. 9 | */ 10 | 11 | /* 12 | * tst.v8whatis.js: exercises the ::v8whatis dcmd. 13 | * 14 | * Like most of the standalone tests, this test works by creating a bunch of 15 | * structures in memory, using gcore(1M) to save a core file of the current 16 | * process, and then using an MDB session against the core file to pull out 17 | * those structures and verify that the debugger interprets them correctly. 18 | */ 19 | 20 | var assert = require('assert'); 21 | var jsprim = require('jsprim'); 22 | var util = require('util'); 23 | var VError = require('verror'); 24 | 25 | var common = require('./common'); 26 | 27 | /* 28 | * "testObject" is the root object from which we will hang the objects used for 29 | * our test cases. Because this test mostly involves walking backwards, the 30 | * actual "test object" that we find with "::findjsobjects" is the one inside 31 | * "myArray". (See the call to finalizeTestObject().) 32 | */ 33 | var testObject = { 34 | 'myArray': [ 1, { 'aTest': 'hello_world' } ] 35 | }; 36 | 37 | /* 38 | * Addresses found in the core file for "testObject" itself as well as the 39 | * arrays hanging off of it. 40 | */ 41 | var addrTestObjectTest; 42 | 43 | function main() 44 | { 45 | var testFuncs; 46 | var addrFixedArray, addrJsArray, addrJsObject; 47 | var addrJsObjectPlus12, addrEndMapping; 48 | var sizeMapping; 49 | 50 | testFuncs = []; 51 | 52 | /* 53 | * First, exercise a few simple cases of invalid input. 54 | */ 55 | testFuncs.push(function badInputNoAddr(mdb, callback) { 56 | console.error('test: bad input: no address'); 57 | mdb.runCmd('::v8whatis\n', function (output, erroutput) { 58 | assert.strictEqual(output, ''); 59 | assert.ok(/must specify address for ::v8whatis/.test( 60 | erroutput)); 61 | callback(); 62 | }); 63 | }); 64 | testFuncs.push(function badInputNoDarg(mdb, callback) { 65 | console.error('test: bad input: no address'); 66 | mdb.runCmd('0::v8whatis -d\n', function (output, erroutput) { 67 | assert.strictEqual(output, ''); 68 | assert.ok(/option requires an argument/.test( 69 | erroutput)); 70 | callback(); 71 | }); 72 | }); 73 | 74 | /* 75 | * Now, locate our test object for the rest of the tests. 76 | */ 77 | testFuncs.push(function findTestObjectAddress(mdb, callback) { 78 | common.findTestObject(mdb, function (err, addr) { 79 | addrTestObjectTest = addr; 80 | callback(err); 81 | }); 82 | }); 83 | 84 | /* 85 | * Walk backwards from the test object. Since it's contained inside an 86 | * array, the immediate parent should be a FixedArray. 87 | */ 88 | testFuncs.push(function walkToFixedArray(mdb, callback) { 89 | console.error( 90 | 'test: walking back from array element to FixedArray'); 91 | walkOneStep(addrTestObjectTest, mdb, function (err, step) { 92 | if (err) { 93 | callback(err); 94 | return; 95 | } 96 | 97 | assert.equal('string', typeof (step.parentBase)); 98 | assert.strictEqual(step.parentType, 'FixedArray'); 99 | addrFixedArray = step.parentBase; 100 | callback(); 101 | }); 102 | }); 103 | 104 | /* 105 | * Walk backwards again from the FixedArray. This should take us to the 106 | * JSArray that contains it. 107 | */ 108 | testFuncs.push(function eltDoUgrep(mdb, callback) { 109 | console.error( 110 | 'test: walking back from FixedArray to JSArray'); 111 | walkOneStep(addrFixedArray, mdb, function (err, step) { 112 | if (err) { 113 | callback(err); 114 | return; 115 | } 116 | 117 | assert.equal('string', typeof (step.parentBase)); 118 | assert.strictEqual(step.parentType, 'JSArray'); 119 | addrJsArray = step.parentBase; 120 | callback(); 121 | }); 122 | }); 123 | 124 | /* 125 | * Now dump the array contents to prove it contains our test object. 126 | */ 127 | testFuncs.push(function checkArrayContents(mdb, callback) { 128 | var cmdstr; 129 | cmdstr = addrJsArray + '::jsarray\n'; 130 | mdb.runCmd(cmdstr, function (output) { 131 | var lines; 132 | lines = common.splitMdbLines(output, { 'count': 2 }); 133 | assert.equal(lines[1], addrTestObjectTest); 134 | callback(); 135 | }); 136 | }); 137 | 138 | /* 139 | * At this point, we've verified a couple of types of references: 140 | * JSArrays and FixedArrays. Let's check object properties by walking 141 | * back once more. Note that this could generate false positives, if V8 142 | * has decided to organize this object differently than it usually does 143 | * (e.g., with a separate "properties" array), but that doesn't seem 144 | * likely here. If it becomes a problem, we can make this test case 145 | * more flexible. 146 | */ 147 | testFuncs.push(function propUgrep1(mdb, callback) { 148 | console.error('test: walking back from JSArray to JSObject'); 149 | walkOneStep(addrJsArray, mdb, function (err, step) { 150 | if (err) { 151 | callback(err); 152 | return; 153 | } 154 | 155 | assert.equal('string', typeof (step.parentBase)); 156 | assert.strictEqual(step.parentType, 'JSObject'); 157 | addrJsObject = step.parentBase; 158 | callback(); 159 | }); 160 | }); 161 | 162 | /* 163 | * Now, print the object contents, following object properties and array 164 | * elements to get back to our original test object, proving that these 165 | * backwards references we found correspond to legitimate forward 166 | * references. 167 | */ 168 | testFuncs.push(function checkObjectContents(mdb, callback) { 169 | var cmdstr; 170 | cmdstr = addrJsObject + '::jsprint myArray[1].aTest\n'; 171 | mdb.runCmd(cmdstr, function (output) { 172 | var lines; 173 | lines = common.splitMdbLines(output, { 'count': 1 }); 174 | assert.ok(/hello_world/.test(lines[0]), 175 | 'bad output for "::jsprint ..."'); 176 | callback(); 177 | }); 178 | }); 179 | 180 | /* 181 | * This next two tests take the address that we now know refers to the 182 | * base of a JSObject and try several pointer values within the object 183 | * that should report the same base address. 184 | */ 185 | testFuncs.push(function objCheckBase(mdb, callback) { 186 | console.error('test: providing base address to ::v8whatis'); 187 | runWhatisVerbose(addrJsObject, mdb, function (step) { 188 | assert.strictEqual(step.parentType, 'JSObject'); 189 | assert.strictEqual(step.parentBase, addrJsObject); 190 | assert.strictEqual(step.symbolicOffset, 191 | addrJsObject + '-0x0'); 192 | callback(); 193 | }); 194 | }); 195 | 196 | testFuncs.push(function objCheckBasePlus12(mdb, callback) { 197 | console.error('test: providing address+12 to ::v8whatis'); 198 | addrJsObjectPlus12 = jsprim.parseInteger(addrJsObject, { 199 | 'base': 16, 200 | 'allowSign': false, 201 | 'allowImprecise': false, 202 | 'allowPrefix': false, 203 | 'allowTrailing': false, 204 | 'trimWhitespace': false, 205 | 'leadingZeroIsOctal': false 206 | }); 207 | if (addrJsObjectPlus12 instanceof Error) { 208 | callback(new VError(addrJsObjectPlus12, 209 | 'could not parse address of test object: %s', 210 | addrTestObjectTest)); 211 | return; 212 | } 213 | 214 | addrJsObjectPlus12 = (addrJsObjectPlus12 + 12).toString(16); 215 | runWhatisVerbose(addrJsObjectPlus12, mdb, function (step) { 216 | assert.strictEqual(step.parentType, 'JSObject'); 217 | assert.strictEqual(step.parentBase, addrJsObject); 218 | assert.strictEqual(step.symbolicOffset, 219 | addrJsObjectPlus12 + '-0xc'); 220 | callback(); 221 | }); 222 | }); 223 | 224 | /* 225 | * On the other hand, if we take this last address (12 bytes into the 226 | * JSObject) and limit our search to only 4 bytes, we should not find a 227 | * parent reference. This exercises the error case where we didn't 228 | * search back far enough. 229 | */ 230 | testFuncs.push(function objCheckBasePlus12Limit4(mdb, callback) { 231 | console.error('test: providing address+12 with limit of 4'); 232 | mdb.runCmd(addrJsObjectPlus12 + '::v8whatis -v -d4\n', 233 | function (output, erroutput) { 234 | assert.ok(/no heap object found in previous 4 bytes/. 235 | test(erroutput)); 236 | assert.strictEqual(output, ''); 237 | callback(); 238 | }); 239 | }); 240 | 241 | /* 242 | * Now test the case where we find a V8 heap object, but it doesn't seem 243 | * to contain our target address. Since V8 often allocates objects 244 | * sequentially without gaps, it can be a little tricky to locate an 245 | * address that we can use to test this case. Here's how we do it: we 246 | * take one of the known-good addresses, find the address at the end of 247 | * its virtual memory mapping, and use that address. We'll use the size 248 | * of the mapping as an argument to "-d". By construction, we know 249 | * there exists a heap object within the specified range, and we know 250 | * that it won't contain our address because it's not even in the same 251 | * mapping (if our address is even mapped at all). 252 | */ 253 | testFuncs.push(function getEndOfMapping(mdb, callback) { 254 | console.error('test: end-of-mapping address'); 255 | mdb.runCmd(addrJsObjectPlus12 + '$m\n', function (output) { 256 | var lines, parts; 257 | 258 | lines = common.splitMdbLines(output, { 'count': 2 }); 259 | parts = lines[1].trim().split(/\s+/); 260 | assert.ok(parts.length >= 3, 'garbled mapping line'); 261 | addrEndMapping = parts[1]; 262 | sizeMapping = parts[2]; 263 | callback(); 264 | }); 265 | }); 266 | testFuncs.push(function useEndOfMapping(mdb, callback) { 267 | mdb.runCmd(addrEndMapping + '::v8whatis -v -d ' + 268 | sizeMapping + '\n', function (output, erroutput) { 269 | assert.ok( 270 | /heap object found.*does not appear to contain/. 271 | test(erroutput)); 272 | assert.strictEqual(output, ''); 273 | callback(); 274 | }); 275 | }); 276 | 277 | testFuncs.push(function (mdb, callback) { 278 | mdb.checkMdbLeaks(callback); 279 | }); 280 | 281 | common.finalizeTestObject(testObject['myArray'][1]); 282 | common.standaloneTest(testFuncs, function (err) { 283 | if (err) { 284 | throw (err); 285 | } 286 | 287 | console.log('%s passed', process.argv[1]); 288 | }); 289 | } 290 | 291 | function walkOneStep(addr, mdb, callback) 292 | { 293 | var rv = { 294 | 'addr': addr, /* address itself */ 295 | 'ugrep': null, /* where address is referenced */ 296 | 'parentBase': null, /* base addr of containing V8 object */ 297 | 'parentType': null, /* type of containing V8 object */ 298 | 'parentRaw': null, /* raw verbose output */ 299 | 'symbolicOffset': null /* offset from "addr" to "parentBase" */ 300 | }; 301 | 302 | mdb.runCmd(addr + '::ugrep\n', function (uoutput) { 303 | var lines; 304 | 305 | lines = common.splitMdbLines(uoutput, { 'count': 1 }); 306 | rv.ugrep = lines[0].trim(); 307 | 308 | mdb.runCmd(rv.ugrep + '::v8whatis\n', 309 | function (woutput, werroutput) { 310 | assert.strictEqual(werroutput.length, 0); 311 | lines = common.splitMdbLines(woutput, { 'count': 1 }); 312 | rv.parentBase = lines[0].trim(); 313 | 314 | runWhatisVerbose(rv.ugrep, mdb, function (whatis) { 315 | assert.strictEqual(rv.parentBase, 316 | whatis.parentBase); 317 | rv.parentType = whatis.parentType; 318 | rv.parentRaw = whatis.parentRaw; 319 | rv.symbolicOffset = whatis.symbolicOffset; 320 | callback(null, rv); 321 | }); 322 | }); 323 | }); 324 | } 325 | 326 | function runWhatisVerbose(addr, mdb, callback) 327 | { 328 | var rv; 329 | 330 | rv = { 331 | 'parentBase': null, 332 | 'parentType': null, 333 | 'parentRaw': null, 334 | 'symbolicOffset': null 335 | }; 336 | 337 | mdb.runCmd(addr + '::v8whatis -v\n', function (output) { 338 | var lines, rex, match; 339 | 340 | lines = common.splitMdbLines(output, { 'count': 1 }); 341 | rv.parentRaw = lines[0]; 342 | rex = new RegExp('^([a-z0-9]+) \\(found Map at ' + 343 | '[a-z0-9]+ \\((.*)\\) for type ([a-zA-Z]+)\\)$'); 344 | match = rv.parentRaw.match(rex); 345 | assert.notStrictEqual(match, null, 'garbled verbose output'); 346 | rv.parentBase = match[1]; 347 | rv.symbolicOffset = match[2]; 348 | rv.parentType = match[3]; 349 | callback(rv); 350 | }); 351 | } 352 | 353 | main(); 354 | -------------------------------------------------------------------------------- /test/test-programs/defer-abort.js: -------------------------------------------------------------------------------- 1 | setImmediate(function doExplicitAbort() { process.abort(); }); 2 | -------------------------------------------------------------------------------- /test/test-programs/explicit-abort.js: -------------------------------------------------------------------------------- 1 | process.abort(); 2 | -------------------------------------------------------------------------------- /test/test-programs/gcore-loop.js: -------------------------------------------------------------------------------- 1 | 2 | function main() 3 | { 4 | func1({ 5 | 'obj1smalldate': new Date(), 6 | 'obj2bigdate': new Date(0), 7 | 'obj3null': null, 8 | 'obj4undef': undefined, 9 | 'obj5num': 5, 10 | 'obj6obj': { 11 | 'obj7arr': [ 1, 7, 7, 6 ] 12 | }, 13 | 'obj8str': 'it was the blurst of times!', 14 | 'obj8re': new RegExp('^hello, "\d+" worlds!$') 15 | }); 16 | } 17 | 18 | function func1(obj) 19 | { 20 | func2(obj); 21 | } 22 | 23 | function func2(obj, extra) 24 | { 25 | for (;;) 26 | ; 27 | console.log(obj); 28 | } 29 | 30 | main(); 31 | -------------------------------------------------------------------------------- /tools/catest: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | # 7 | 8 | # 9 | # Copyright (c) 2014, Joyent, Inc. 10 | # 11 | 12 | # 13 | # catest: a simple testing tool and framework. See usage below for details. 14 | # 15 | 16 | shopt -s xpg_echo 17 | 18 | # 19 | # Global configuration 20 | # 21 | cat_arg0=$(basename $0) # canonical name of "catest" 22 | cat_outbase="catest.$$" # output directory name 23 | cat_tstdir="test" # test directory 24 | 25 | # 26 | # Options and arguments 27 | # 28 | cat_tests="" # list of tests (absolute paths) 29 | opt_a=false # run all tests 30 | opt_c=false # colorize test results 31 | opt_k=false # keep output of successful tests 32 | opt_o="/var/tmp" # parent directory for output directory 33 | opt_t= # TAP format output file 34 | opt_S=false # Non-strict mode for js tests 35 | 36 | # 37 | # Current state 38 | # 39 | cat_outdir= # absolute path to output directory 40 | cat_tapfile= # absolute path of TAP output file 41 | cat_ntests= # total number of tests 42 | cat_nfailed=0 # number of failed tests run 43 | cat_npassed=0 # number of successful tests run 44 | cat_nrun=0 # total number of tests run 45 | 46 | # 47 | # Allow environment-specific customizations. 48 | # 49 | [[ -f $(dirname $0)/catest_init.sh ]] && . $(dirname $0)/catest_init.sh 50 | 51 | # 52 | # fail MSG: emits the given error message to stderr and exits non-zero. 53 | # 54 | function fail 55 | { 56 | echo "$cat_arg0: $@" >&2 57 | 58 | [[ -n $cat_tapfile ]] && echo "Bail out! $@" >> $cat_tapfile 59 | 60 | exit 1 61 | } 62 | 63 | # 64 | # usage [MSG]: emits the given message, if any, and a usage message, then exits. 65 | # 66 | function usage 67 | { 68 | [[ $# -ne 0 ]] && echo "$cat_arg0: $@\n" >&2 69 | 70 | cat <&2 71 | Usage: $cat_arg0 [-k] [-c] [-o dir] [-t file] test1 ... 72 | $cat_arg0 [-k] [-c] [-o dir] [-t file] -a 73 | 74 | In the first form, runs specified tests. In the second form, runs all tests 75 | found under "$cat_tstdir" of the form "tst*." for supported extensions. 76 | 77 | TESTS 78 | 79 | Tests are just files to be executed by some interpreter. In most cases, a 80 | test succeeds if it exits successfully and fails otherwise. You can also 81 | specify the expected stdout of the test in a file with the same name as the 82 | test plus a ".out" suffix, in which case the test will also fail if the 83 | actual output does not match the expected output. 84 | 85 | Supported interpreter extensions are "sh" (bash) and "js" (node). 86 | 87 | This framework does not provide per-test setup/teardown facilities, but 88 | test files can do whatever they want, including making use of common 89 | libraries for setup and teardown. 90 | 91 | TEST OUTPUT 92 | 93 | Summary output is printed to stdout. TAP output can be emitted with "-t". 94 | 95 | Per-test output is placed in a new temporary directory inside the directory 96 | specified by the -o option, or /var/tmp if -o is not specified. 97 | 98 | Within the output directory will be a directory for each failed test which 99 | includes a README describing why the test failed (e.g., exited non-zero), a 100 | copy of the test file itself, the actual stdout and stderr of the test, and 101 | the expected stdout of the test (if specified). 102 | 103 | If -k is specified, the output directory will also include a directory for 104 | each test that passed including the stdout and stderr from the test. 105 | 106 | The following options may be specified: 107 | 108 | -a Runs all tests under $cat_tstdir 109 | (ignores other non-option arguments) 110 | -c Color code test result messages 111 | -h Output this message 112 | -k Keep output from all tests, not just failures 113 | -o directory Specifies the output directory for tests 114 | (default: /var/tmp) 115 | -S Turn off strict mode for tests 116 | -t file Emit summary output in TAP format 117 | 118 | USAGE 119 | 120 | exit 2 121 | } 122 | 123 | # 124 | # abspath FILE: emits a canonical, absolute path to the given file or directory. 125 | # 126 | function abspath 127 | { 128 | local dir=$(dirname $1) base=$(basename $1) 129 | 130 | if [[ $base = ".." ]]; then 131 | cd "$dir"/.. > /dev/null || fail "abspath '$1': failed to chdir" 132 | pwd 133 | cd - > /dev/null || fail "abspath '$1': failed to chdir back" 134 | else 135 | cd "$dir" || fail "abspath '$1': failed to chdir" 136 | echo "$(pwd)/$base" 137 | cd - > /dev/null || fail "abspath '$1': failed to chdir back" 138 | fi 139 | } 140 | 141 | # 142 | # cleanup_test TESTDIR "success" | "failure": cleans up the output directory 143 | # for this test 144 | # 145 | function cleanup_test 146 | { 147 | local test_odir="$1" result=$2 148 | local newdir 149 | 150 | if [[ $result = "success" ]]; then 151 | newdir="$(dirname $test_odir)/success.$cat_npassed" 152 | else 153 | newdir="$(dirname $test_odir)/failure.$cat_nfailed" 154 | fi 155 | 156 | mv "$test_odir" "$newdir" 157 | echo $newdir 158 | } 159 | 160 | # 161 | # emit_failure TEST ODIR REASON: indicate that a test has failed 162 | # 163 | function emit_failure 164 | { 165 | local test_label=$1 odir=$2 reason=$3 166 | 167 | if [[ $cat_tapfile ]]; then 168 | echo "not ok $(($cat_nrun+1)) $test_label" >> $cat_tapfile 169 | fi 170 | 171 | echo "${TRED}FAILED.${TCLEAR}" 172 | echo "$test_path failed: $reason" > "$odir/README" 173 | 174 | [[ -n "$odir" ]] && echo ">>> failure details in $odir\n" 175 | ((cat_nfailed++)) 176 | } 177 | 178 | # 179 | # emit_pass TEST: indicate that a test has passed 180 | # 181 | function emit_pass 182 | { 183 | local test_label=$1 184 | 185 | if [[ $cat_tapfile ]]; then 186 | echo "ok $((cat_nrun+1)) $test_label" >> $cat_tapfile 187 | fi 188 | 189 | echo "${TGREEN}success.${TCLEAR}" 190 | ((cat_npassed++)) 191 | } 192 | 193 | # 194 | # Executes a single test 195 | # 196 | # Per-test actions: 197 | # - Make a directory for that test 198 | # - cd into that directory and exec the test 199 | # - Redirect standard output and standard error to files 200 | # - Tests return 0 to indicate success, non-zero to indicate failure 201 | # 202 | function execute_test 203 | { 204 | [[ $# -eq 1 ]] || fail "Missing test to execute" 205 | local test_path=$1 206 | local test_name=$(basename $1) 207 | local test_dir=$(dirname $1) 208 | local test_label=$(echo $test_path | sed -e s#^$SRC/##) 209 | local test_odir="$cat_outdir/test.$cat_nrun" 210 | local ext=${test_name##*.} 211 | local faildir 212 | local EXEC 213 | 214 | echo "Executing test $test_label ... \c " 215 | mkdir "$test_odir" >/dev/null || fail "failed to create test directory" 216 | cp "$test_path" "$test_odir" 217 | 218 | case "$ext" in 219 | "sh") EXEC=bash ;; 220 | "js") EXEC=node ;; 221 | *) faildir=$(cleanup_test "$test_odir" "failure") 222 | emit_failure "$test_label" "$faildir" "unknown file extension" 223 | return 0 224 | ;; 225 | esac 226 | 227 | pushd "$test_dir" >/dev/null 228 | if [[ $opt_S ]]; then 229 | $EXEC $test_name -S >$test_odir/$$.out 2>$test_odir/$$.err 230 | else 231 | $EXEC $test_name >$test_odir/$$.out 2>$test_odir/$$.err 232 | fi 233 | execres=$? 234 | popd > /dev/null 235 | 236 | if [[ $execres != 0 ]]; then 237 | faildir=$(cleanup_test "$test_odir" "failure") 238 | emit_failure "$test_label" "$faildir" "test returned $execres" 239 | return 0 240 | fi 241 | 242 | if [[ -f $test_path.out ]] && \ 243 | ! diff $test_path.out $test_odir/$$.out > /dev/null ; then 244 | cp $test_path.out $test_odir/$test_name.out 245 | faildir=$(cleanup_test "$test_odir" "failure") 246 | emit_failure "$test_label" "$faildir" "stdout mismatch" 247 | return 0 248 | fi 249 | 250 | cleanup_test "$test_odir" "success" > /dev/null 251 | emit_pass "$test_label" 252 | } 253 | 254 | while getopts ":o:t:ackSh?" c $@; do 255 | case "$c" in 256 | a|c|k|S) eval opt_$c=true ;; 257 | o|t) eval opt_$c="$OPTARG" ;; 258 | h) usage ;; 259 | :) usage "option requires an argument -- $OPTARG" ;; 260 | *) usage "invalid option: $OPTARG" ;; 261 | esac 262 | done 263 | 264 | # 265 | # If configured to use terminal colors, record the escape sequences here. 266 | # 267 | if [[ $opt_c == "true" && -t 1 ]]; then 268 | TGREEN="$(tput setaf 2)" 269 | TRED="$(tput setaf 1)" 270 | TCLEAR="$(tput sgr0)" 271 | fi 272 | 273 | shift $((OPTIND-1)) 274 | [[ $# -eq 0 && $opt_a == "false" ]] && \ 275 | usage "must specify \"-a\" or list of tests" 276 | 277 | # 278 | # Initialize paths and other environment variables. 279 | # 280 | export SRC=$(abspath $(dirname $0)/..) 281 | export PATH=$SRC/deps/ctf2json:$PATH 282 | [[ -n $HOST ]] || export HOST=$(hostname) 283 | 284 | # 285 | # We create and set CATMPDIR as a place for the tests to store temporary files. 286 | # 287 | export CATMPDIR="/var/tmp/catest.$$_tmpfiles" 288 | 289 | if [[ $opt_a = "true" ]]; then 290 | cat_tests=$(find $SRC/$cat_tstdir \ 291 | -name 'tst*.js' -o -name 'tst*.sh') || \ 292 | fail "failed to locate tests in $SRC/$cat_tstdir" 293 | cat_tests=$(sort <<< "$cat_tests") 294 | else 295 | for t in $@; do 296 | [[ -f $t ]] || fail "cannot find test $t" 297 | cat_tests="$cat_tests $(abspath $t)" 298 | done 299 | fi 300 | 301 | mkdir -p "$opt_o/$cat_outbase" 302 | cat_outdir=$(abspath $opt_o/$cat_outbase) 303 | 304 | mkdir -p $CATMPDIR || fail "failed to create $CATMPDIR" 305 | 306 | cat_ntests=$(echo $cat_tests | wc -w) 307 | printf "Configuration:\n" 308 | printf " SRC: $SRC\n" 309 | printf " Output directory: $cat_outdir\n" 310 | printf " Temp directory: $CATMPDIR\n" 311 | if [[ -n "$opt_t" ]]; then 312 | cat_tapfile=$(abspath $opt_t) 313 | printf " TAP output: $cat_tapfile\n" 314 | fi 315 | printf " Keep successful test output: $opt_k\n" 316 | printf " Found %d test(s) to run\n\n" $cat_ntests 317 | 318 | # 319 | # Validate parameters and finish setup. 320 | # 321 | [[ $cat_ntests -gt 0 ]] || fail "no tests found" 322 | 323 | if [[ -n "$cat_tapfile" ]]; then 324 | echo "1..$(($cat_ntests))" > $cat_tapfile || \ 325 | fail "failed to emit TAP output" 326 | fi 327 | 328 | # 329 | # Allow for environment-specific customizations. These are optionally loaded 330 | # by the catest_init.sh file sourced earlier. 331 | # 332 | if type catest_init > /dev/null 2>&1 && ! catest_init; then 333 | fail "catest_init failed" 334 | fi 335 | 336 | # 337 | # Start the test run. 338 | # 339 | printf "===================================================\n\n" 340 | 341 | for t in $cat_tests; do 342 | execute_test $t 343 | ((cat_nrun++)) 344 | done 345 | 346 | printf "\n===================================================\n\n" 347 | printf "Results:\n" 348 | printf "\tTests passed:\t%2d/%2d\n" $cat_npassed $cat_nrun 349 | printf "\tTests failed:\t%2d/%2d\n" $cat_nfailed $cat_nrun 350 | printf "\n===================================================\n" 351 | 352 | if [[ $opt_k == "false" ]]; then 353 | echo "Cleaning up output from successful tests ... \c " 354 | rm -rf $cat_outdir/success.* 355 | rm -rf $CATMPDIR 356 | echo "done." 357 | fi 358 | 359 | exit $cat_nfailed 360 | -------------------------------------------------------------------------------- /tools/dumpjsobjects: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # dumpjsobjects CORE_FILE DMOD_FILE OBJ_LIST OBJ_CONTENTS 5 | # 6 | # Dump all JavaScript objects in CORE_FILE using mdb_v8 binary DMOD_FILE. The 7 | # sorted list of all objects is stored into the file OBJ_LIST and the contents 8 | # of all objects is stored into the file OBJ_CONTENTS. 9 | # 10 | 11 | set -o pipefail 12 | 13 | function usage 14 | { 15 | cat <&2 16 | usage: dumpjsobjects CORE_FILE DMOD_FILE OBJ_LIST OBJ_CONTENTS 17 | 18 | Dump all JavaScript objects in CORE_FILE using mdb_v8 binary DMOD_FILE. The 19 | sorted list of all objects is stored into the file OBJ_LIST and the contents of 20 | all objects is stored into the file OBJ_CONTENTS. 21 | EOF 22 | exit 2 23 | } 24 | 25 | function main 26 | { 27 | local listcmd printcmd 28 | 29 | if [[ $# != 4 ]]; then 30 | usage 31 | fi 32 | 33 | listcmd="::findjsobjects -l | ::findjsobjects ! sort > $3" 34 | printcmd="::cat $3 | ::jsprint -a -d 2 -N 0t100 ! cat > $4" 35 | set -o xtrace 36 | if ! mdb -e "::load $2; $listcmd" "$1" || 37 | ! mdb -e "::load $2; $printcmd" "$1"; then 38 | echo "FAILED." >&2 39 | exit 1 40 | fi 41 | 42 | echo "done." 43 | } 44 | 45 | main "$@" 46 | -------------------------------------------------------------------------------- /tools/jsl.node.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration File for JavaScript Lint 3 | # 4 | # This configuration file can be used to lint a collection of scripts, or to enable 5 | # or disable warnings for scripts that are linted via the command line. 6 | # 7 | 8 | ### Warnings 9 | # Enable or disable warnings based on requirements. 10 | # Use "+WarningName" to display or "-WarningName" to suppress. 11 | # 12 | +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent 13 | +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity 14 | +ambiguous_newline # unexpected end of line; it is ambiguous whether these lines are part of the same statement 15 | +anon_no_return_value # anonymous function does not always return value 16 | +assign_to_function_call # assignment to a function call 17 | -block_without_braces # block statement without curly braces 18 | +comma_separated_stmts # multiple statements separated by commas (use semicolons?) 19 | +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) 20 | +default_not_at_end # the default case is not at the end of the switch statement 21 | +dup_option_explicit # duplicate "option explicit" control comment 22 | +duplicate_case_in_switch # duplicate case in switch statement 23 | +duplicate_formal # duplicate formal argument {name} 24 | +empty_statement # empty statement or extra semicolon 25 | +identifier_hides_another # identifer {name} hides an identifier in a parent scope 26 | -inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement 27 | +incorrect_version # Expected /*jsl:content-type*/ control comment. The script was parsed with the wrong version. 28 | +invalid_fallthru # unexpected "fallthru" control comment 29 | +invalid_pass # unexpected "pass" control comment 30 | +jsl_cc_not_understood # couldn't understand control comment using /*jsl:keyword*/ syntax 31 | +leading_decimal_point # leading decimal point may indicate a number or an object member 32 | +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax 33 | +meaningless_block # meaningless block; curly braces have no impact 34 | +mismatch_ctrl_comments # mismatched control comment; "ignore" and "end" control comments must have a one-to-one correspondence 35 | +misplaced_regex # regular expressions should be preceded by a left parenthesis, assignment, colon, or comma 36 | +missing_break # missing break statement 37 | +missing_break_for_last_case # missing break statement for last case in switch 38 | +missing_default_case # missing default case in switch statement 39 | +missing_option_explicit # the "option explicit" control comment is missing 40 | +missing_semicolon # missing semicolon 41 | +missing_semicolon_for_lambda # missing semicolon for lambda assignment 42 | +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs 43 | +nested_comment # nested comment 44 | +no_return_value # function {name} does not always return a value 45 | +octal_number # leading zeros make an octal number 46 | +parseint_missing_radix # parseInt missing radix parameter 47 | +partial_option_explicit # the "option explicit" control comment, if used, must be in the first script tag 48 | +redeclared_var # redeclaration of {name} 49 | +trailing_comma_in_array # extra comma is not recommended in array initializers 50 | +trailing_decimal_point # trailing decimal point may indicate a number or an object member 51 | +undeclared_identifier # undeclared identifier: {name} 52 | +unreachable_code # unreachable code 53 | -unreferenced_argument # argument declared but never referenced: {name} 54 | -unreferenced_function # function is declared but never referenced: {name} 55 | +unreferenced_variable # variable is declared but never referenced: {name} 56 | +unsupported_version # JavaScript {version} is not supported 57 | +use_of_label # use of label 58 | +useless_assign # useless assignment 59 | +useless_comparison # useless comparison; comparing identical expressions 60 | -useless_quotes # the quotation marks are unnecessary 61 | +useless_void # use of the void type may be unnecessary (void is always undefined) 62 | +var_hides_arg # variable {name} hides argument 63 | +want_assign_or_call # expected an assignment or function call 64 | +with_statement # with statement hides undeclared variables; use temporary variable instead 65 | 66 | 67 | ### Output format 68 | # Customize the format of the error message. 69 | # __FILE__ indicates current file path 70 | # __FILENAME__ indicates current file name 71 | # __LINE__ indicates current line 72 | # __COL__ indicates current column 73 | # __ERROR__ indicates error message (__ERROR_PREFIX__: __ERROR_MSG__) 74 | # __ERROR_NAME__ indicates error name (used in configuration file) 75 | # __ERROR_PREFIX__ indicates error prefix 76 | # __ERROR_MSG__ indicates error message 77 | # 78 | # For machine-friendly output, the output format can be prefixed with 79 | # "encode:". If specified, all items will be encoded with C-slashes. 80 | # 81 | # Visual Studio syntax (default): 82 | +output-format __FILE__(__LINE__): __ERROR__ 83 | # Alternative syntax: 84 | #+output-format __FILE__:__LINE__: __ERROR__ 85 | 86 | 87 | ### Context 88 | # Show the in-line position of the error. 89 | # Use "+context" to display or "-context" to suppress. 90 | # 91 | +context 92 | 93 | 94 | ### Control Comments 95 | # Both JavaScript Lint and the JScript interpreter confuse each other with the syntax for 96 | # the /*@keyword@*/ control comments and JScript conditional comments. (The latter is 97 | # enabled in JScript with @cc_on@). The /*jsl:keyword*/ syntax is preferred for this reason, 98 | # although legacy control comments are enabled by default for backward compatibility. 99 | # 100 | -legacy_control_comments 101 | 102 | 103 | ### Defining identifiers 104 | # By default, "option explicit" is enabled on a per-file basis. 105 | # To enable this for all files, use "+always_use_option_explicit" 106 | -always_use_option_explicit 107 | 108 | # Define certain identifiers of which the lint is not aware. 109 | # (Use this in conjunction with the "undeclared identifier" warning.) 110 | # 111 | # Common uses for webpages might be: 112 | +define __dirname 113 | +define clearInterval 114 | +define clearTimeout 115 | +define console 116 | +define exports 117 | +define global 118 | +define module 119 | +define process 120 | +define require 121 | +define setImmediate 122 | +define clearImmediate 123 | +define setInterval 124 | +define setTimeout 125 | +define Buffer 126 | +define JSON 127 | +define Math 128 | +define __dirname 129 | +define __filename 130 | 131 | ### JavaScript Version 132 | # To change the default JavaScript version: 133 | #+default-type text/javascript;version=1.5 134 | #+default-type text/javascript;e4x=1 135 | 136 | ### Files 137 | # Specify which files to lint 138 | # Use "+recurse" to enable recursion (disabled by default). 139 | # To add a set of files, use "+process FileName", "+process Folder\Path\*.js", 140 | # or "+process Folder\Path\*.htm". 141 | # 142 | 143 | -------------------------------------------------------------------------------- /tools/mdbv8diff/issues/issue-35.js: -------------------------------------------------------------------------------- 1 | /* 2 | * issue-35.js: mdbv8diff module for the changes for issue 35. 3 | * 4 | * This file contains logic that's used when diff'ing mdb_v8 output from two 5 | * versions, one before the change for issue #35, and one after. This is 6 | * basically useful only for regression testing. Issue #35 rewrote a bunch of 7 | * internal interfaces in a way that was mostly logically equivalent, but fixed 8 | * several existing issues. This module attempts to distinguish between diffs 9 | * where output changed in an expected way and those where it may have 10 | * regressed. 11 | */ 12 | 13 | exports.ignoreDiff = ignoreDiff; 14 | exports.ignoreAddress = ignoreAddress; 15 | 16 | /* 17 | * Returns true if mdbv8diff should treat "value1" and "value2" (which are known 18 | * not to be the exact same string already) as equivalent. (That would be 19 | * because they changed in a known-good way.) 20 | */ 21 | function ignoreDiff(stream, value1, value2) 22 | { 23 | var fixed; 24 | 25 | /* 26 | * This function has a lot of regular expressions that confuse jsstyle. 27 | */ 28 | /* BEGIN JSSTYLED */ 29 | 30 | /* 31 | * Many of the difference across the fix for issue #35 result from more 32 | * consistent (usually more specific) error messages. 33 | */ 34 | if (//.test(value1) || 35 | //.test(value1) || 36 | //.test(value1) || 44 | //.test(value2) || 47 | //.test(value2) || 48 | //.test(value2) || 49 | //.test(value2)) { 50 | stream.vsCounterBump('diff_more_specific_string_error'); 51 | return (true); 52 | } 53 | } 54 | 55 | if (//. 56 | test(value1) && 57 | /""/.test(value2)) { 58 | stream.vsCounterBump('diff_external_string_error'); 59 | return (true); 60 | } 61 | 62 | /* 63 | * The following two checks build on each other. Do not change the 64 | * order without reading the code carefully. 65 | */ 66 | fixed = value1.replace( 67 | //, 68 | ''); 69 | if (fixed != value1 && value2 == fixed) { 70 | stream.vsCounterBump('diff_sliced_parent'); 71 | return (true); 72 | } 73 | 74 | fixed = fixed.replace( 75 | //, 76 | '""'); 77 | if (fixed != value1 && value2 == fixed) { 78 | stream.vsCounterBump('diff_sliced_parent_quoted'); 79 | return (true); 80 | } 81 | 82 | /* 83 | * Similarly, the following two checks build on each other. 84 | */ 85 | fixed = value1.replace( 86 | //, 87 | ''); 88 | if (fixed != value1 && value2 == fixed) { 89 | stream.vsCounterBump('diff_sliced_parent_seq'); 90 | return (true); 91 | } 92 | 93 | fixed = fixed.replace( 94 | //, 95 | '""'); 96 | if (fixed != value1 && value2 == fixed) { 97 | stream.vsCounterBump('diff_sliced_parent_seq_quote'); 98 | return (true); 99 | } 100 | 101 | /* 102 | * Some of those differences just relate to quoting. 103 | */ 104 | fixed = value2.replace( 105 | /""/, 106 | ''); 107 | if (fixed != value2 && value1 == fixed) { 108 | stream.vsCounterBump('diff_proper_quoting'); 109 | return (true); 110 | } 111 | 112 | fixed = value2.replace( 113 | /""/, 114 | ''); 115 | if (fixed != value2 && value1 == fixed) { 116 | stream.vsCounterBump('diff_proper_quoting'); 117 | return (true); 118 | } 119 | 120 | /* END JSSTYLED */ 121 | 122 | /* 123 | * Check for changes in string truncation. 124 | */ 125 | if (differOnlyInTruncation(stream, value1, value2)) { 126 | stream.vsCounterBump('diff_truncation'); 127 | return (true); 128 | } 129 | 130 | return (false); 131 | } 132 | 133 | /* 134 | * Checks whether the two values differ only in the way either the strings 135 | * themselves or any substrings used therein have been truncated. When mdb_v8 136 | * truncates strings, it inserts a marker "[...]". This has changed across 137 | * versions: 138 | * 139 | * - Prior to the implementation of issue #35, mdb_v8 would often truncate 140 | * strings one character shorter than necessary. Output for the same 141 | * string from old and new versions of mdb_v8 may be off-by-one in the 142 | * position of the truncation marker: 143 | * 144 | * actual string: "foo_bartholomew" 145 | * old: "foo_bar[...]" 146 | * new: "foo_bart[...]" 147 | * 148 | * - As a result of the same behavioral difference, if this 1-byte difference 149 | * is right at the border of the string's length, this could result in a 150 | * string that isn't truncated at all on newer versions: 151 | * 152 | * actual string: "foo_bart" 153 | * old: "foo_bar[...]" 154 | * new: "foo_bart" 155 | * 156 | * - In either of the previous two cases, in some cases the older mdb_v8 157 | * would leave out the trailing closing quote ('"') from the output. 158 | * 159 | * - Prior to the implementation of issue #35, mdb_v8 would NOT insert the 160 | * "[...]" marker when truncating external ASCII strings. 161 | * 162 | * To summarize: 163 | * 164 | * - either string may have any number of truncation markers 165 | * 166 | * - either string may have truncation markers that the other doesn't 167 | * 168 | * - the second string may have an arbitrary extra character before the 169 | * truncation marker 170 | * 171 | * - the second string may have an extra '"' character before the truncation 172 | * marker 173 | * 174 | * Our goal here is to ignore these differences (and _only_ these differences). 175 | * This implementation is exponential in the number of markers in both strings, 176 | * but in practice that's extremely small. 177 | */ 178 | function differOnlyInTruncation(stream, value1, value2) 179 | { 180 | var i, p, q, reason; 181 | var v1before, v1after; 182 | var v2before, v2after; 183 | 184 | p = value1.indexOf('[...]'); 185 | q = value2.indexOf('[...]'); 186 | if (p == -1) { 187 | if (q == -1) { 188 | /* 189 | * Neither string has a truncation. This is a base 190 | * case. The differ logically only if they actually 191 | * differ. 192 | */ 193 | return (value1 == value2); 194 | } 195 | 196 | /* 197 | * Truncations in the second string are more constrained than 198 | * truncations in the first string -- there are no funny edge 199 | * cases with the surrounding characters, and the part of 200 | * "value1" that corresponds to the truncation marker must be 201 | * the same length as the marker. (If it were shorter, it 202 | * wouldn't have been truncated on newer versions. If it were 203 | * longer, it wouldn't have fit in the same buffer as the 204 | * marker.) 205 | */ 206 | v2before = value2.substr(0, q); 207 | v1before = value1.substr(0, q); 208 | if (v1before != v2before) { 209 | return (false); 210 | } 211 | 212 | /* 213 | * We always need to make a recursive call for the tails of the 214 | * strings in case there are more truncation markers. 215 | */ 216 | v2after = value2.substr(q + '[...]'.length); 217 | v1after = value1.substr(q + '[...]'.length); 218 | return (differOnlyInTruncation(stream, v1after, v2after)); 219 | } 220 | 221 | v1before = value1.substr(0, p); 222 | v1after = value1.substr(p + '[...]'.length); 223 | 224 | if (q != -1 && (q == p || q == p + 1 || q == p + 2)) { 225 | /* 226 | * Both strings have a truncation marker, and they're within 2 227 | * characters of the same position. They must represent the 228 | * same truncation. 229 | * 230 | * We basically want to compare what's before and after the 231 | * marker, but there are edge cases on either side, so it's 232 | * easier to break these checks out separately. 233 | * 234 | * First, compare the substrings before the "[...]" marker. 235 | * Remember, the second substring may have an extra character or 236 | * two, in which case we'll ignore it. 237 | */ 238 | v2before = value2.substr(0, p); 239 | 240 | if (v1before != v2before) { 241 | /* 242 | * The strings differ in more than just truncation 243 | * because the parts before the truncation mark don't 244 | * match up (even allowing for the possible extra 245 | * characters in the second string). 246 | */ 247 | return (false); 248 | } 249 | 250 | /* 251 | * Now, compare the substrings after the "[...]" marker. 252 | * Remember, the second substring may have an extra '"' 253 | * character. 254 | */ 255 | v2after = value2.substr(q + '[...]'.length); 256 | if (v2after.charAt(0) == '"' && v1after.charAt(0) != '"') { 257 | v2after = value2.substr(q + '[...]"'.length); 258 | reason = 'trunc_accurate_truncation_plus_quote'; 259 | } else { 260 | reason = 'trunc_accurate_truncation'; 261 | } 262 | 263 | if (!differOnlyInTruncation(stream, v1after, v2after)) { 264 | return (false); 265 | } 266 | 267 | stream.vsCounterBump(reason); 268 | return (true); 269 | } 270 | 271 | /* 272 | * The second string either does not have a truncation marker or it 273 | * appears to be much later in the string. In either case, we're going 274 | * to ignore it until the recursive call. 275 | * 276 | * As above, we'll first check everything up to the truncation mark in 277 | * the first string. 278 | */ 279 | v2before = value2.substr(0, p); 280 | if (v1before != v2before) { 281 | /* 282 | * The strings don't match up even before we get to the 283 | * truncation marker. 284 | */ 285 | return (false); 286 | } 287 | 288 | /* 289 | * Now check that the tails of the strings match. There may be one or 290 | * two extra characters, and sadly, we have to check both. 291 | */ 292 | for (i = 1; i <= 2; i++) { 293 | v2after = value2.substr(p + '[...]'.length + i); 294 | if (v2after.charAt(0) == '"' && v1after.charAt(0) != '"') { 295 | v2after = v2after.substr(1); 296 | reason = 'trunc_unneccessary_truncation_plus_quote'; 297 | } else { 298 | reason = 'trunc_unneccessary_truncation'; 299 | } 300 | 301 | if (differOnlyInTruncation(stream, v1after, v2after)) { 302 | stream.vsCounterBump(reason); 303 | return (true); 304 | } 305 | } 306 | 307 | return (false); 308 | } 309 | 310 | /* 311 | * This function is even more specific: it assumes a particular core file that's 312 | * used for regression testing. That's not ideal, but it _is_ actually useful 313 | * for these known cases to be documented somewhere. 314 | */ 315 | function ignoreAddress(stream, addr) 316 | { 317 | var ignore = false; 318 | 319 | switch (addr) { 320 | case '82741231': 321 | /* 322 | * Subproperty fd268e71 ("_events") is a SlicedString whose 323 | * slice is way past the end of its parent string. The value 324 | * should be the empty string, but old versions erroneously 325 | * printed some characters here. 326 | */ 327 | ignore = true; 328 | stream.vsCounterBump('ignored_SlicedString'); 329 | break; 330 | 331 | case '827b6d4d': 332 | case '88df1c99': 333 | /* 334 | * In previous versions of mdb_v8, 827b6d4d ran into a now-fixed 335 | * bug in read_heap_dict() that would emit a bogus property. 336 | * 827b6d4d refers to 88df1c99, so its output has the same 337 | * problem. 338 | */ 339 | ignore = true; 340 | stream.vsCounterBump('ignored_read_heap_dict'); 341 | break; 342 | 343 | case 'b44f2651': 344 | /* 345 | * b44f2651 runs into mdb_v8 issue #49. It's broken in older 346 | * code, and it's broken in newer code, but the failure mode is 347 | * slightly different, so it shows up spuriously in the diff. 348 | * Ignore it. 349 | */ 350 | ignore = true; 351 | stream.vsCounterBump('ignored_issue-49'); 352 | break; 353 | 354 | default: 355 | break; 356 | } 357 | 358 | return (ignore); 359 | } 360 | -------------------------------------------------------------------------------- /tools/mdbv8diff/mdbv8diff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * mdbv8diff [-i IGNORE] FILE1 FILE2: given FILE1 and FILE2 as test output from 5 | * mdb_v8, report differences. If IGNORE is given, this contains a small Node 6 | * module that describes kinds of changes that should be ignored. Typically, 7 | * one would write a new ignore file for each major change being 8 | * regression-tested with this tool. 9 | * 10 | * This tool is intended for regression testing across versions. The input 11 | * files should be generated using the "dumpjsobjects" tool. See the dev 12 | * documentation for details. 13 | * 14 | * This tool should be viewed as an extra way to help validate a change. It's 15 | * not a substitute for careful code review, manual testing of affected changes, 16 | * and automated testing. 17 | */ 18 | 19 | var mod_assertplus = require('assert-plus'); 20 | var mod_cmdutil = require('cmdutil'); 21 | var mod_events = require('events'); 22 | var mod_fs = require('fs'); 23 | var mod_getopt = require('posix-getopt'); 24 | var mod_lstream = require('lstream'); 25 | var mod_path = require('path'); 26 | var mod_stream = require('stream'); 27 | var mod_util = require('util'); 28 | var mod_vstream = require('vstream'); 29 | 30 | var sprintf = require('extsprintf').sprintf; 31 | var VError = require('verror'); 32 | 33 | var JoinStream = require('./vstream-join'); 34 | 35 | function main() 36 | { 37 | var parser, option; 38 | var ignoreFile, ignorePath, ignoreModule; 39 | var source1, source2; 40 | var join, diff, serializer; 41 | 42 | mod_cmdutil.configure({ 43 | 'usageMessage': 'Compare output from mdb_v8.', 44 | 'synopses': [ '[-i IGNORE] FILE1 FILE2' ] 45 | }); 46 | 47 | ignoreFile = null; 48 | parser = new mod_getopt.BasicParser('i:', process.argv); 49 | while ((option = parser.getopt()) !== undefined) { 50 | switch (option.option) { 51 | case 'i': 52 | ignoreFile = option.optarg; 53 | break; 54 | 55 | default: 56 | /* error message already emitted by getopt */ 57 | mod_assertplus.equal('?', option.option); 58 | mod_cmdutil.usage(); 59 | break; 60 | } 61 | } 62 | 63 | if (ignoreFile !== null) { 64 | try { 65 | ignorePath = mod_path.resolve(ignoreFile); 66 | ignoreModule = require(ignorePath); 67 | } catch (err) { 68 | mod_cmdutil.fail(new VError(err, 69 | 'loading "%s"', ignoreFile)); 70 | } 71 | } else { 72 | ignoreModule = null; 73 | } 74 | 75 | if (parser.optind() + 1 >= process.argv.length) { 76 | mod_cmdutil.usage(); 77 | } 78 | 79 | source1 = createInputStream(process.argv[parser.optind()]); 80 | source2 = createInputStream(process.argv[parser.optind() + 1]); 81 | join = new JoinStream({ 82 | 'joinOnIndex': 0, 83 | 'sources': [ source1, source2 ] 84 | }); 85 | 86 | diff = new DiffStream({ 87 | 'labels': [ process.argv[2], process.argv[3] ], 88 | 'ignoreModule': ignoreModule 89 | }); 90 | join.pipe(diff); 91 | 92 | serializer = new DiffStreamSerializer(); 93 | diff.pipe(serializer); 94 | serializer.pipe(process.stdout); 95 | 96 | process.stdout.on('error', function (err) { 97 | if (err['code'] == 'EPIPE') 98 | return; 99 | throw (err); 100 | }); 101 | 102 | diff.on('warn', function (context, kind, error) { 103 | mod_cmdutil.warn(error.message); 104 | console.error(' at ', context.label()); 105 | }); 106 | 107 | diff.on('end', function () { 108 | diff.vsDumpCounters(process.stderr); 109 | }); 110 | } 111 | 112 | /* 113 | * Returns the tail of a stream pipeline that goes: 114 | * 115 | * File read stream (path) -> line parser -> mdbv8 parser 116 | */ 117 | function createInputStream(path) 118 | { 119 | var filestream, lstream, parser; 120 | 121 | filestream = mod_fs.createReadStream(path); 122 | filestream.on('error', function (err) { 123 | mod_cmdutil.fail(new VError(err, 'open "%s"', path)); 124 | }); 125 | 126 | lstream = new mod_lstream(); 127 | mod_vstream.wrapTransform(lstream); 128 | filestream.pipe(lstream); 129 | 130 | parser = new ParserStream(); 131 | lstream.pipe(parser); 132 | 133 | return (parser); 134 | } 135 | 136 | function ParserStream() 137 | { 138 | this.ps_accum = []; 139 | mod_stream.Transform.call(this, { 'objectMode': true }); 140 | mod_vstream.wrapTransform(this); 141 | } 142 | 143 | mod_util.inherits(ParserStream, mod_stream.Transform); 144 | 145 | ParserStream.prototype._transform = function (chunk, _, callback) 146 | { 147 | /* There should be an lstream in front of this stream. */ 148 | mod_assertplus.equal(chunk.indexOf('\n'), -1); 149 | 150 | this.ps_accum.push(chunk.trim()); 151 | 152 | /* 153 | * This gnarly regexp matches top-level values that are empty objects 154 | * or empty arrays (e.g., "{}" and "[]"). 155 | */ 156 | if (chunk.charAt(0) == '}' || chunk.charAt(0) == ']' || 157 | /* JSSTYLED */ 158 | /^[0-9a-fA-F]+: [{[][}\]]/.test(chunk)) { 159 | this.valueDone(); 160 | } 161 | 162 | setImmediate(callback); 163 | }; 164 | 165 | ParserStream.prototype._flush = function (callback) 166 | { 167 | if (this.ps_accum.length > 0) 168 | this.valueDone(); 169 | setImmediate(callback); 170 | }; 171 | 172 | ParserStream.prototype.valueDone = function () 173 | { 174 | var lines, line, err, colon, addr; 175 | var parsed, opener; 176 | 177 | mod_assertplus.ok(this.ps_accum.length > 0); 178 | lines = this.ps_accum; 179 | this.ps_accum = []; 180 | 181 | line = lines[lines.length - 1]; 182 | if (line != ']' && line != '}') { 183 | err = new VError('unrecognized value'); 184 | this.vsWarn(err, 'unrecognized value'); 185 | return; 186 | } 187 | 188 | line = lines[0]; 189 | colon = line.indexOf(':'); 190 | if (colon == -1) { 191 | err = new VError('value has no address'); 192 | this.vsWarn(err, 'value has no address'); 193 | return; 194 | } 195 | 196 | /* 197 | * "Parse" is a strong word for what we're doing. If we wanted to do 198 | * this reliably, we should get mdb_v8 emitting parseable output. We're 199 | * getting there, but the point of this tool is to help identify changes 200 | * in the existing, not-very-parseable output. It's just supposed to be 201 | * a little less unwieldy than diff(1). So we basically just break 202 | * things out on a by-object basis, compare lines one-by-one, and ignore 203 | * known changes. 204 | */ 205 | addr = line.substr(0, colon); 206 | parsed = {}; 207 | parsed['addr'] = addr; 208 | opener = line.charAt(colon + ': '.length); 209 | if (opener == '{') { 210 | parsed['type'] = 'object'; 211 | parsed['lines'] = lines.slice(1); 212 | this.push([ addr, parsed ]); 213 | } else if (opener == '[') { 214 | parsed['type'] = 'array'; 215 | parsed['lines'] = lines.slice(1); 216 | this.push([ addr, parsed ]); 217 | } else { 218 | err = new VError('unknown type'); 219 | this.vsWarn(err, 'unknown type'); 220 | } 221 | }; 222 | 223 | 224 | function DiffStream(args) 225 | { 226 | mod_assertplus.object(args, 'args'); 227 | mod_assertplus.arrayOfString(args.labels, 'args.labels'); 228 | mod_assertplus.equal(args.labels.length, 2); 229 | mod_assertplus.optionalObject(args.ignoreModule, 'args.ignoreModule'); 230 | 231 | mod_stream.Transform.call(this, { 'objectMode': true }); 232 | mod_vstream.wrapTransform(this); 233 | 234 | this.ds_labels = args.labels.slice(0); 235 | this.ds_ignore = args.ignoreModule || null; 236 | } 237 | 238 | mod_util.inherits(DiffStream, mod_stream.Transform); 239 | 240 | DiffStream.prototype._transform = function (joined, _, callback) 241 | { 242 | var rowlabel, label1, label2; 243 | var value1, value2; 244 | var i, max; 245 | 246 | if (joined.length != 3) { 247 | this.vsWarn(new Error('garbled value'), 'garbled value'); 248 | setImmediate(callback); 249 | return; 250 | } 251 | 252 | mod_assertplus.string(joined[0], 'joined[0]'); 253 | rowlabel = joined[0]; 254 | 255 | if (this.ignoreAddress(rowlabel)) { 256 | setImmediate(callback); 257 | return; 258 | } 259 | 260 | label1 = this.ds_labels[0]; 261 | value1 = joined[1]; 262 | 263 | label2 = this.ds_labels[1]; 264 | value2 = joined[2]; 265 | 266 | if (value1 === null) { 267 | this.push({ 268 | 'label': rowlabel, 269 | 'message': sprintf('only in %s', label2) 270 | }); 271 | return; 272 | } 273 | if (value2 === null) { 274 | this.push({ 275 | 'label': rowlabel, 276 | 'message': sprintf('only in %s', label1) 277 | }); 278 | return; 279 | } 280 | 281 | value1 = value1[1]; 282 | value2 = value2[1]; 283 | if (value1['type'] != value2['type']) { 284 | this.push({ 285 | 'label': rowlabel, 286 | 'messages': [ { 287 | 'source': label1, 288 | 'message': sprintf('type %s', value1['type']) 289 | }, { 290 | 'source': label2, 291 | 'message': sprintf('type %s', value2['type']) 292 | } ] 293 | }); 294 | 295 | return; 296 | } 297 | 298 | /* 299 | * Compare lines one-by-one. Really, the only difference 300 | * between this tool and diff(1) is that we're going to ignore 301 | * certain known differences. 302 | */ 303 | max = Math.max(value1['lines'].length, value2['lines'].length); 304 | for (i = 0; i < max; i++) { 305 | if (i >= value1['lines'].length) { 306 | this.push({ 307 | 'label': rowlabel, 308 | 'message': sprintf('line %d: property missing ' + 309 | 'from "%s"', i + 1, label1) 310 | }); 311 | } else if (i >= value2['lines'].length) { 312 | this.push({ 313 | 'label': rowlabel, 314 | 'message': sprintf('line %d: property missing ' + 315 | 'from "%s"', i + 1, label2) 316 | }); 317 | } else if (value1['lines'][i] != value2['lines'][i] && 318 | !this.ignoreDiff(value1['lines'][i], value2['lines'][i])) { 319 | this.push({ 320 | 'label': rowlabel, 321 | 'messages': [ { 322 | 'source': label1, 323 | 'message': sprintf('line %d: %s\n', 324 | i + 1, value1['lines'][i]) 325 | }, { 326 | 'source': label2, 327 | 'message': sprintf('line %d: %s\n', 328 | i + 1, value2['lines'][i]) 329 | } ] 330 | }); 331 | } 332 | } 333 | 334 | setImmediate(callback); 335 | }; 336 | 337 | DiffStream.prototype.ignoreAddress = function (addr) 338 | { 339 | if (this.ds_ignore !== null) { 340 | return (this.ds_ignore.ignoreAddress(this, addr)); 341 | } 342 | 343 | return (false); 344 | }; 345 | 346 | DiffStream.prototype.ignoreDiff = function (value1, value2) 347 | { 348 | if (this.ds_ignore !== null) { 349 | return (this.ds_ignore.ignoreDiff(this, value1, value2)); 350 | } 351 | 352 | return (false); 353 | }; 354 | 355 | 356 | function DiffStreamSerializer() 357 | { 358 | mod_stream.Transform.call(this, { 'objectMode': true }); 359 | } 360 | 361 | mod_util.inherits(DiffStreamSerializer, mod_stream.Transform); 362 | 363 | DiffStreamSerializer.prototype._transform = function (obj, _, callback) 364 | { 365 | var self = this; 366 | 367 | if (obj.hasOwnProperty('message')) { 368 | self.push(sprintf('%s: %s\n', obj['label'], obj['message'])); 369 | } else { 370 | obj['messages'].forEach(function (m) { 371 | self.push(sprintf('%s: %s: %s', 372 | obj['label'], m['source'], m['message'])); 373 | }); 374 | } 375 | 376 | setImmediate(callback); 377 | }; 378 | 379 | 380 | main(); 381 | -------------------------------------------------------------------------------- /tools/mdbv8diff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdbv8diff", 3 | "version": "1.0.0", 4 | "description": "diff output from mdbv8 for testing", 5 | "main": "mdbv8diff", 6 | "private": true, 7 | "dependencies": { 8 | "assert-plus": "0.1.5", 9 | "cmdutil": "0.1.0", 10 | "extsprintf": "1.3.0", 11 | "jsprim": "1.2.2", 12 | "lstream": "0.0.4", 13 | "posix-getopt": "1.2.0", 14 | "verror": "1.6.0", 15 | "vstream": "0.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tools/mdbv8diff/vstream-join.js: -------------------------------------------------------------------------------- 1 | /* 2 | * XXX This should be extracted into a separate Node module. 3 | */ 4 | /* 5 | * A JoinStream takes as constructor inputs a group of sorted object-mode 6 | * streams and joins them. For example, two input streams might contain: 7 | * 8 | * INPUT STREAM 1 INPUT STREAM 2 9 | * [ id1, 7 ] [ id2, 35 ] 10 | * [ id2, 18 ] [ id3, 12 ] 11 | * 12 | * If you join these on the the first column, the output of the stream would 13 | * look like this: 14 | * 15 | * OUTPUT STREAM 16 | * [ id1, [ id1, 7 ], null ] 17 | * [ id2, [ id2, 18 ], [id2, 35] ] 18 | * [ id3, null, [id3, 12] ] 19 | * 20 | * The stream identifies mis-sorts and emits an error. 21 | */ 22 | 23 | var mod_assertplus = require('assert-plus'); 24 | var mod_jsprim = require('jsprim'); 25 | var mod_stream = require('stream'); 26 | var mod_util = require('util'); 27 | var mod_vstream = require('vstream'); 28 | var VError = require('verror'); 29 | 30 | module.exports = JoinStream; 31 | 32 | /* 33 | * Joins N object-mode streams that emit arrays, one element of which is the 34 | * join key. Values are assumed to be sorted by the join key. This stream 35 | * emits joined objects of the form: 36 | * 37 | * [ join key, value from stream 1, value from stream 2, ... ] 38 | * 39 | * Named arguments include: 40 | * 41 | * joinOnIndex integer, index of each input on which to join 42 | * 43 | * sources list of streams to read from. Each stream should emit 44 | * an array that has the joinOnIndex value. 45 | * 46 | * [streamOptions] options to pass to stream constructor 47 | */ 48 | function JoinStream(args) 49 | { 50 | var streamoptions; 51 | var self = this; 52 | 53 | mod_assertplus.object(args, 'args'); 54 | mod_assertplus.number(args.joinOnIndex, 'args.joinOnIndex'); 55 | mod_assertplus.arrayOfObject(args.sources, 'args.sources'); 56 | mod_assertplus.optionalObject(args.streamOptions, 'args.streamOptions'); 57 | mod_assertplus.ok(args.sources.length > 0); 58 | 59 | streamoptions = mod_jsprim.mergeObjects(args.streamOptions, 60 | { 'objectMode': true }, { 'highWaterMark': 0 }); 61 | mod_stream.Readable.call(this, streamoptions); 62 | mod_vstream.wrapStream(this); 63 | 64 | this.js_joinidx = args.joinOnIndex; 65 | this.js_waiting = false; 66 | this.js_working = false; 67 | this.js_sources = args.sources.map(function (s) { 68 | var src = { 69 | 'src_stream': s, 70 | 'src_next': null, 71 | 'src_next_join': null, 72 | 'src_last_join': null, 73 | 'src_done': false 74 | }; 75 | 76 | s.on('readable', function sourceReadable() { 77 | self.kickIfWaiting(); 78 | }); 79 | 80 | s.on('end', function sourceEnded() { 81 | src.src_done = true; 82 | self.kickIfWaiting(); 83 | }); 84 | 85 | return (src); 86 | }); 87 | } 88 | 89 | mod_util.inherits(JoinStream, mod_stream.Readable); 90 | 91 | /* 92 | * Initially, js_waiting is false. js_waiting becomes true when we run out of 93 | * data from our upstream sources, but we still have room to emit more data 94 | * ourselves. When an upstream source becomes readable or ended AND js_waiting 95 | * is set, then js_waiting is cleared and we read as much as possible and push 96 | * until either we block again (and js_waiting is set again) or we run out of 97 | * output buffer space. 98 | * 99 | * The framework only invokes _read() if it previously indicated we were out of 100 | * buffer space. In that case, js_waiting should not generally be set, since 101 | * that means we last came to rest because of buffer space exhaustion rather 102 | * than waiting for upstream data. Nevertheless, Node appears to do this 103 | * sometimes, so we defensively just do nothing in that case. This is a little 104 | * scary, since incorrectness here can result in the program exiting zero 105 | * without having read all the data, but the surrounding program verifies that 106 | * this doesn't happen. 107 | */ 108 | JoinStream.prototype._read = function () 109 | { 110 | if (this.js_working || this.js_waiting) { 111 | return; 112 | } 113 | 114 | this.kick(); 115 | }; 116 | 117 | /* 118 | * kickIfWaiting() is invoked when an upstream source either becomes readable or 119 | * ended. If we're currently waiting for upstream data, then we kick ourselves 120 | * to try to process whatever we've got. Otherwise, we do nothing. 121 | */ 122 | JoinStream.prototype.kickIfWaiting = function () 123 | { 124 | if (!this.js_waiting) { 125 | return; 126 | } 127 | 128 | this.js_waiting = false; 129 | this.kick(); 130 | }; 131 | 132 | /* 133 | * kick() is invoked when we may have more data to process. This happens in one 134 | * of two cases: 135 | * 136 | * o When the framework wants the next datum from the stream. This happens 137 | * when the consumer first calls read() (since there is no data buffered) 138 | * and when the consumer subsequently calls read() and the buffer is 139 | * empty. In principle, this could also happen even with some data 140 | * already buffered if the framework just decides it wants to buffer more 141 | * data in advance of a subsequent read. 142 | * 143 | * The important thing in this case is that we're transitioning from being 144 | * at rest because the framework wants no more data (i.e., because of 145 | * backpressure) to actively reading data from our upstream sources. 146 | * 147 | * o When we were previously blocked on an upstream source, and an upstream 148 | * source now has data (or end-of-stream) available. In this case, we're 149 | * transitioning from being at rest because we were blocked on an upstream 150 | * read to actively reading because at least one upstream source is now 151 | * ready. 152 | * 153 | * This function decides what to do based on the current state. If there's data 154 | * (or end-of-stream) available from all sources, it constructs the next output 155 | * and emits it. It repeats this until either we get backpressure from the 156 | * framework or one of the upstream sources has no data available. 157 | * 158 | * In principle, it would be possible to invoke this at any time, but for 159 | * tightness, we're careful to keep track of which case we're in. We maintain 160 | * the invariant that when kick() is called, js_waiting must be cleared. 161 | */ 162 | JoinStream.prototype.kick = function () 163 | { 164 | var i, src, datum, blocked; 165 | var joinkey, output, err; 166 | var keepgoing; 167 | 168 | mod_assertplus.ok(!this.js_waiting); 169 | mod_assertplus.ok(!this.js_working); 170 | 171 | /* 172 | * Iterate the input streams and make sure we have the next row 173 | * available from each of them. If we don't, we'll be blocked and won't 174 | * be able to make forward progress until we do. 175 | */ 176 | blocked = false; 177 | for (i = 0; i < this.js_sources.length; i++) { 178 | src = this.js_sources[i]; 179 | if (src.src_next !== null || src.src_done) { 180 | continue; 181 | } 182 | 183 | datum = src.src_stream.read(); 184 | if (datum === null) { 185 | blocked = blocked || !src.src_done; 186 | continue; 187 | } 188 | 189 | mod_assertplus.ok(Array.isArray(datum)); 190 | src.src_next = datum; 191 | src.src_next_join = datum[this.js_joinidx]; 192 | 193 | if (src.src_next_join < src.src_last_join) { 194 | err = new VError('source %d: out of order', i); 195 | this.vsWarn(err, 'out of order'); 196 | this.emit('error', err); 197 | return; 198 | } 199 | } 200 | 201 | if (blocked) { 202 | this.js_waiting = true; 203 | return; 204 | } 205 | 206 | /* 207 | * By this point, we have the next row from each source. Figure out the 208 | * earliest join key among them. 209 | */ 210 | joinkey = null; 211 | for (i = 0; i < this.js_sources.length; i++) { 212 | src = this.js_sources[i]; 213 | if (src.src_next_join !== null && 214 | (joinkey === null || joinkey > src.src_next_join)) { 215 | joinkey = src.src_next_join; 216 | } 217 | } 218 | 219 | if (joinkey === null) { 220 | mod_assertplus.equal(0, this.js_sources.filter( 221 | function (s) { return (!s.src_done); }).length); 222 | this.js_working = true; 223 | this.push(null); 224 | this.js_working = false; 225 | return; 226 | } 227 | 228 | /* 229 | * Now, emit one array value which contains: 230 | * 231 | * o the join key itself 232 | * o the arrays representing the corresponding values from each source, 233 | * or "null" for sources that don't contain the join key 234 | */ 235 | output = [ joinkey ]; 236 | for (i = 0; i < this.js_sources.length; i++) { 237 | src = this.js_sources[i]; 238 | if (joinkey == src.src_next_join) { 239 | output.push(src.src_next); 240 | src.src_last_join = src.src_next_join; 241 | src.src_next_join = null; 242 | src.src_next = null; 243 | } else { 244 | output.push(null); 245 | } 246 | } 247 | 248 | this.js_working = true; 249 | keepgoing = this.push(output); 250 | this.js_working = false; 251 | 252 | if (keepgoing) { 253 | this.kick(); 254 | } 255 | }; 256 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node_modules.defs: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2017, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile.node_modules.defs: Makefile for using NPM modules. 13 | # 14 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 15 | # into other repos as-is without requiring any modifications. If you find 16 | # yourself changing this file, you should instead update the original copy in 17 | # eng.git and then update your repo to use the new version. 18 | # 19 | 20 | # 21 | # This Makefile provides a target for building NPM modules from the dependency 22 | # information in the "package.json" file. The "npm install" operation is 23 | # expensive and produces a complex (multi-file) result which is difficult for 24 | # make to use in dependency analysis. As such, we use a "stamp" file to track 25 | # successful completion of module installation. 26 | # 27 | # This variable allows the consumer to influence the environment used to run 28 | # NPM commands. 29 | # 30 | # NPM_ENV This string should be set to a list of 31 | # environment variables in the syntax used 32 | # by bash; e.g., 33 | # 34 | # NPM_ENV = TESTING=yes V=1 35 | # 36 | # Consumers should, for targets which depend on the installation of NPM 37 | # modules, depend on the stamp file using the $(STAMP_NODE_MODULES) variable, 38 | # e.g.: 39 | # 40 | # .PHONY: all 41 | # all: $(STAMP_NODE_MODULES) 42 | # 43 | # A phony target, "make stamp-node-modules", is also provided to allow the 44 | # engineer to manually perform NPM module installation without invoking other 45 | # targets. Note that this target should _not_ be used as a dependency for 46 | # other targets in consuming Makefiles; using phony targets to represent 47 | # intermediate build stages can inhibit the ability of make to determine 48 | # when no additional actions are required. 49 | # 50 | 51 | TOP ?= $(error You must include Makefile.defs before this makefile) 52 | NPM ?= $(error You must include either Makefile.node.defs or \ 53 | Makefile.node_prebuilt.defs before this makefile) 54 | 55 | BUILD ?= build 56 | 57 | # 58 | # Invoking "npm install" at the top-level will create a "node_modules" 59 | # directory into which NPM modules will be installed. 60 | # 61 | CLEAN_FILES += node_modules 62 | 63 | # 64 | # To avoid repeatedly reinstalling from NPM, we create a "stamp" file to track 65 | # successful runs of "npm install". Note that MAKE_STAMPS_DIR is included 66 | # in CLEAN_FILES already. 67 | # 68 | STAMP_NODE_MODULES ?= $(MAKE_STAMPS_DIR)/node-modules 69 | -------------------------------------------------------------------------------- /tools/mk/Makefile.node_modules.targ: -------------------------------------------------------------------------------- 1 | # 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | 7 | # 8 | # Copyright (c) 2017, Joyent, Inc. 9 | # 10 | 11 | # 12 | # Makefile.node_modules.targ: See comments in Makefile.node_modules.defs. 13 | # 14 | # NOTE: This makefile comes from the "eng" repo. It's designed to be dropped 15 | # into other repos as-is without requiring any modifications. If you find 16 | # yourself changing this file, you should instead update the original copy in 17 | # eng.git and then update your repo to use the new version. 18 | # 19 | 20 | STAMP_NODE_MODULES ?= $(error You must include Makefile.node_modules.defs \ 21 | before this file) 22 | 23 | # 24 | # If the "package.json" file changes, we need to rebuild the contents of 25 | # the "node_modules" directory. 26 | # 27 | $(STAMP_NODE_MODULES): package.json | $(NPM_EXEC) 28 | $(MAKE_STAMP_REMOVE) 29 | rm -rf node_modules 30 | $(NPM_ENV) $(NPM) install 31 | $(MAKE_STAMP_CREATE) 32 | -------------------------------------------------------------------------------- /tools/mkversion: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This Source Code Form is subject to the terms of the Mozilla Public 5 | # License, v. 2.0. If a copy of the MPL was not distributed with this 6 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | # 8 | 9 | # 10 | # Copyright (c) 2015, Joyent, Inc. 11 | # 12 | 13 | # 14 | # tools/mkversion: given the contents of the "version" file on stdin, emit a C 15 | # file on stdout that defines appropriate values for the version major, minor, 16 | # and micro numbers. Lines beginning with '#' are ignored in the input. Only 17 | # the first non-ignored line will be used. 18 | # 19 | 20 | awk -F. '!/^#/{ print $1, $2, $3 }' | head -1 | while read major minor micro; do 21 | cat <<-EOF 22 | /* 23 | * This file was generated by $0 at $(date). 24 | */ 25 | int mdbv8_vers_major = $major; 26 | int mdbv8_vers_minor = $minor; 27 | int mdbv8_vers_micro = $micro; 28 | EOF 29 | done 30 | -------------------------------------------------------------------------------- /tools/publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # This Source Code Form is subject to the terms of the Mozilla Public 5 | # License, v. 2.0. If a copy of the MPL was not distributed with this 6 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | # 8 | 9 | # 10 | # Copyright (c) 2015, Joyent, Inc. 11 | # 12 | 13 | # 14 | # publish: publish a new version of mdb_v8. This does a few local sanity 15 | # checks, creates a git tag for the current version, and uploads bits to Manta. 16 | # To publish a new version, run: 17 | # 18 | # $ git status # check that working tree is not dirty 19 | # $ make release # makes "clean" first, then "all" 20 | # $ ./tools/publish # sanity checks, git tags, upload 21 | # # use "-l" option if this is a new latest 22 | # # release. 23 | # 24 | # Then follow the suggested steps output by the above script. 25 | # 26 | 27 | arg0="$(basename ${BASH_SOURCE[0]})" 28 | p_root="/Joyent_Dev/public/mdb_v8" 29 | p_latest=false 30 | p_version= 31 | p_dir= 32 | 33 | function usage 34 | { 35 | [[ -n "$@" ]] && echo "$arg0: $@" >&2 36 | cat >&2 <<-EOF 37 | usage: $arg0 [-fl] 38 | 39 | Publishes the currently built version of mdb_v8 to Manta. 40 | EOF 41 | exit 2 42 | } 43 | 44 | function fail 45 | { 46 | echo "$arg0: $@" >&2 47 | exit 1 48 | } 49 | 50 | # 51 | # confirm MESSAGE: prompt the user with MESSAGE and returns success only if 52 | # they reply with "y" or "Y". 53 | # 54 | function confirm 55 | { 56 | # Prompt the user with the message we were given. 57 | read -p "$@" -n 1 58 | 59 | # Print a newline, regardless of what they typed. 60 | echo 61 | 62 | # Return success iff the user typed "y" or "Y". 63 | [[ $REPLY =~ ^[Yy]$ ]] 64 | } 65 | 66 | function main 67 | { 68 | while getopts ":l" c "$@"; do 69 | case "$c" in 70 | l) p_latest=true ;; 71 | :) usage "option requires an argument -- $OPTARG" ;; 72 | *) usage "invalid option: $OPTARG" ;; 73 | esac 74 | done 75 | 76 | cd "$(dirname "${BASH_SOURCE[0]}")/.." || fail "failed to cd" 77 | 78 | echo -n "Checking for local copy of Manta tools ... " 79 | if ! type "mls" > /dev/null 2>&1; then 80 | fail "mls(1) not found" 81 | fi 82 | echo "okay." 83 | 84 | pub_load_version 85 | pub_sanity_check 86 | 87 | if ! pub_create_tag; then 88 | confirm "Failed to apply tag. Continue? " || \ 89 | fail "aborted by user" 90 | fi 91 | 92 | if pub_exists_upstream; then 93 | confirm "Overwrite published version? " || \ 94 | fail "aborted by user" 95 | fi 96 | 97 | pub_publish 98 | 99 | if [[ "$p_latest" == "true" ]]; then 100 | pub_update_latest 101 | echo "Done." 102 | else 103 | echo -n "Done. " 104 | echo "Did not update \"latest\" link (use -l to do that)." 105 | fi 106 | 107 | echo "" 108 | echo "Suggested next steps:" 109 | echo " - push new tag using \"git push --tags\"" 110 | echo " - make \"clean\" to avoid re-publishing release bits" 111 | echo " - update \"version\" file for next development version" 112 | echo " - commit that change and push it upstream" 113 | } 114 | 115 | # 116 | # Load into p_version the semver version number (e.g., "1.2.3"). 117 | # 118 | function pub_load_version 119 | { 120 | echo -n "Loading version number ... " 121 | p_version="$(grep -v '^#' version | head -1)" 122 | [[ -n "$p_version" ]] || fail "failed to read version" 123 | echo "$p_version." 124 | p_dir="$p_root/v$p_version" 125 | } 126 | 127 | # 128 | # Run pre-publish sanity checks on the built binaries. 129 | # 130 | function pub_sanity_check 131 | { 132 | pub_sanity_check_file build/ia32/mdb_v8.so 133 | pub_sanity_check_file build/amd64/mdb_v8.so 134 | } 135 | 136 | function pub_sanity_check_file 137 | { 138 | local file tmpfile pid i tag version 139 | 140 | file="$1" 141 | echo -n "Checking for $file ... " 142 | if [[ ! -f "$file" ]]; then 143 | fail "not found" 144 | else 145 | echo "done." 146 | fi 147 | 148 | # 149 | # This is incredibly circuitous (not to mention lame), but we want to 150 | # try to pull a specific string out of the binary, which is not all that 151 | # easy to do. 152 | # 153 | tmpfile=/var/tmp/mdbv8publish.$$ 154 | rm -f /var/tmp/mdbv8publish.$$ 155 | echo -n "Checking for release tag on "$file" ... " 156 | mdb -S -e '::load '"$file"'; !touch '"$tmpfile"'; ! sleep 300' \ 157 | "$file" > /dev/null 2>&1 & 158 | pid="$!" 159 | for (( i = 0; i < 30; i++ )) { 160 | if [[ -f "$tmpfile" ]]; then 161 | break 162 | fi 163 | 164 | sleep 1 165 | } 166 | [[ -f "$tmpfile" ]] || fail "failed" 167 | rm -f "$tmpfile" 168 | tag="$(mdb -S -p "$pid" -e '*mdbv8_vers_tag/s' | awk '{print $2}' | 169 | sed -e s'#,##g')" 170 | kill "$pid" > /dev/null 2>&1 171 | echo "\"$tag\"" 172 | if [[ "$tag" != "release" ]]; then 173 | fail "does not appear to be a release build (tag "\"$tag\"")" 174 | fi 175 | } 176 | 177 | # 178 | # Create a git tag for the current version. 179 | # 180 | function pub_create_tag 181 | { 182 | echo -n "Creating git tag \"v$p_version\" ... " 183 | if ! git tag -a "v$p_version" -m "v$p_version"; then 184 | return 1 185 | fi 186 | echo "done." 187 | } 188 | 189 | # 190 | # Check whether the given release exists upstream. 191 | # 192 | function pub_exists_upstream 193 | { 194 | local mls output 195 | 196 | echo -n "Listing upstream versions ... " 197 | output="$(mls -j "$p_root" | json -ga name)" || \ 198 | fail "failed to list upstream versions" 199 | echo "done." 200 | 201 | echo -n "Checking whether $p_version already exists ... " 202 | if echo "$output" | grep "^v$p_version\$" > /dev/null 2>&1; then 203 | echo "yes" 204 | return 0 205 | else 206 | echo "no" 207 | return 1 208 | fi 209 | } 210 | 211 | # 212 | # Upload the bits to Manta. 213 | # 214 | function pub_publish 215 | { 216 | echo -n "Uploading bits to Manta ... " 217 | if ! mmkdir -p "$p_dir" || \ 218 | ! mput -f build/ia32/mdb_v8.so "$p_dir/mdb_v8_ia32.so" || \ 219 | ! mput -f build/amd64/mdb_v8.so "$p_dir/mdb_v8_amd64.so"; then 220 | fail "failed" 221 | fi 222 | } 223 | 224 | # 225 | # Update the "latest" links in Manta. 226 | # 227 | function pub_update_latest 228 | { 229 | echo "$p_dir" | mput "$p_root/latest" 230 | } 231 | 232 | main "$@" 233 | -------------------------------------------------------------------------------- /tools/runtests_node: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # runtests_node: runs the mdb_v8 test suite with several versions of Node. 5 | # See usage() for usage details. 6 | # 7 | 8 | set -o pipefail 9 | 10 | # 11 | # Static configuration: Node versions to setup and test. 12 | # 13 | rn_versions="0.10.48 0.12.17 4.8.4 6.11.2" 14 | rn_platform="sunos" 15 | rn_arches="x86 x64" 16 | rn_nodebase="https://nodejs.org/dist" 17 | 18 | # 19 | # Runtime configuration 20 | # 21 | rn_arg0="$(basename ${BASH_SOURCE[0]})" 22 | rn_srcroot="$(dirname ${BASH_SOURCE[0]})/.." 23 | rn_subcmd= 24 | rn_target_dir= 25 | rn_results= 26 | rn_nerrors=0 27 | 28 | # 29 | # usage MESSAGE...: emit MESSAGE to stderr and exit non-zero. 30 | # 31 | function usage 32 | { 33 | echo "$rn_arg0: $@" >&2 34 | cat <&2 35 | usage: $rn_arg0 setup TARGET_DIR 36 | $rn_arg0 run TARGET_DIR 37 | 38 | Runs the mdb_v8 test suite with several versions of Node. There are two 39 | subcommands for this program: 40 | 41 | setup sets up directory TARGET_DIR with the configured Node versions 42 | by downloading official builds 43 | 44 | run runs the test suite with the configured Node versions. You should 45 | have already run "setup" with TARGET_DIR first. 46 | 47 | EOF 48 | exit 2 49 | } 50 | 51 | # 52 | # fail MESSAGE...: emit MESSAGE to stderr and exit 1 53 | # 54 | function fail 55 | { 56 | echo "$rn_arg0: $@" >&2 57 | exit 1 58 | } 59 | 60 | function main 61 | { 62 | local func 63 | 64 | if [[ $# -lt 2 ]]; then 65 | usage "missing arguments" 66 | elif [[ $# -gt 2 ]]; then 67 | usage "extra arguments" 68 | fi 69 | 70 | cd "$rn_srcroot" || fail "failed to fetch source root" 71 | rn_srcroot="$(pwd)" 72 | cd - > /dev/null || fail "failed to fetch source root" 73 | 74 | rn_subcmd="$1" 75 | rn_target_dir="$2" 76 | case "$rn_subcmd" in 77 | setup|run) do_$rn_subcmd ;; 78 | *) usage "unknown command: \"$rn_subcmd\"" ;; 79 | esac 80 | } 81 | 82 | # 83 | # do_setup: downloads and unpacks all configured Node versions into the target 84 | # directory. 85 | # 86 | function do_setup 87 | { 88 | if ! mkdir -p "$rn_target_dir"; then 89 | fail "failed to create \"$rn_target_dir\"" 90 | fi 91 | 92 | if ! cd "$rn_target_dir"; then 93 | fail "failed to chdir \"$rn_target_dir\"" 94 | fi 95 | 96 | for version in $rn_versions; do 97 | for arch in $rn_arches; do 98 | if ! do_setup_one \ 99 | "$version" "$rn_platform" "$arch"; then 100 | fail "failed to setup Node $version ($arch)" 101 | fi 102 | done 103 | done 104 | } 105 | 106 | # 107 | # do_setup_one VERSION PLATFORM ARCH: download and unpack a specific Node 108 | # version into the current directory. 109 | # 110 | function do_setup_one 111 | { 112 | local version platform arch 113 | local dir file url 114 | 115 | version="$1" 116 | platform="$2" 117 | arch="$3" 118 | 119 | dir="node-v$version-$platform-$arch" 120 | file="$dir.tar.gz" 121 | url="$rn_nodebase/v$version/$file" 122 | 123 | if [[ -d "$dir" ]]; then 124 | echo "Skipping Node version $version" \ 125 | "($platform $arch) -- already found"; 126 | return 0 127 | fi 128 | 129 | echo -n "Setting up Node version $version ($platform $arch) ... " 130 | 131 | if ! curl -fsS -o $file "$url"; then 132 | echo "download failed." 133 | return 1 134 | fi 135 | 136 | if ! tar xzf $file; then 137 | echo "extract failed." 138 | return 1 139 | fi 140 | 141 | rm -f $file 142 | echo "done." 143 | } 144 | 145 | # 146 | # do_run: run mdb_v8 tests on all configured Node versions 147 | # 148 | function do_run 149 | { 150 | if ! cd "$rn_target_dir" 2>/dev/null; then 151 | fail "failed to chdir \"$rn_target_dir\"" \ 152 | "(have you run \"setup\"?)" 153 | fi 154 | 155 | rn_target_dir="$(pwd)" 156 | cd - > /dev/null 157 | 158 | cd "$rn_srcroot" || fail "failed to cd \"$rn_srcroot\"" 159 | for version in $rn_versions; do 160 | for arch in $rn_arches; do 161 | do_run_one "$version" "$rn_platform" "$arch" 162 | done 163 | done 164 | 165 | echo 166 | echo "Summary:" 167 | echo -e "$rn_results" 168 | exit "$rn_nerrors" 169 | } 170 | 171 | # 172 | # do_run_one VERSION PLATFORM ARCH: run tests with the given Node version. 173 | # 174 | function do_run_one 175 | { 176 | local version platform arch 177 | local dir result 178 | 179 | version="$1" 180 | platform="$2" 181 | arch="$3" 182 | 183 | dir="$rn_target_dir/node-v$version-$platform-$arch" 184 | if [[ ! -d "$dir" || ! -f "$dir/bin/node" ]]; then 185 | fail "Did not find $dir/bin/node (have you run \"setup\"?)" 186 | fi 187 | 188 | if ( 189 | export PATH="$dir/bin:$PATH" 190 | node -pe '"node " + process.version + " " + process.arch' 191 | tools/catest -a 192 | ); then 193 | result="$(printf "%-7s %5s %3s: success" \ 194 | "$version" "$platform" "$arch")" 195 | else 196 | result="$(printf "%-7s %5s %3s: fail" \ 197 | "$version" "$platform" "$arch")" 198 | rn_nerrors=$(( rn_nerrors + 1 )) 199 | fi 200 | 201 | rn_results="$rn_results$result\n" 202 | } 203 | 204 | main "$@" 205 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | # 2 | # This file is processed by tools/mkversion. The value here denotes the current 3 | # version of the debugger module. Normal (non-release) builds will be tagged 4 | # with this version, plus a "dev" tag that indicates the version is not final. 5 | # For a release build, a "release" tag will be applied. While there may be many 6 | # "dev" builds with the same version, there should be only one canonical 7 | # "release" build with a given version number. 8 | # 9 | # This value is also used by the "publish" target in the build process. 10 | # 11 | 1.4.4 12 | --------------------------------------------------------------------------------