diff --git a/Makefile b/Makefile index 702cf98..fe2976f 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,5 @@ all: cd padre && cargo build + +clean: + cd padre && cargo clean diff --git a/autoload/padre/buffer.vim b/autoload/padre/buffer.vim index 500c00b..5e19989 100644 --- a/autoload/padre/buffer.vim +++ b/autoload/padre/buffer.vim @@ -19,8 +19,9 @@ endfunction function! padre#buffer#SetMainPadreKeyBindingsForCurrentBuffer() nnoremap r :PadreRun - nnoremap S :PadreStepIn - nnoremap s :PadreStepOver + nnoremap S :execute v:count1 . " PadreStepIn" + nnoremap s :execute v:count1 . " PadreStepOver" + nnoremap C-s :PadreStepOut vnoremap p y:PadrePrintVariable " nnoremap C :PadreContinue nnoremap ZZ :PadreStop @@ -30,6 +31,7 @@ function! padre#buffer#UnsetPadreKeyBindingsForCurrentBuffer() nnoremap r r nnoremap S S nnoremap s s + nnoremap C-s C-s vnoremap p p nnoremap C C nnoremap ZZ ZZ diff --git a/autoload/padre/debugger.vim b/autoload/padre/debugger.vim index e9a189f..ed79c65 100644 --- a/autoload/padre/debugger.vim +++ b/autoload/padre/debugger.vim @@ -135,7 +135,7 @@ function! padre#debugger#Debug(...) endfunction function! padre#debugger#Run() - call padre#socket#Send({"cmd": "run"}, function('padre#debugger#RunCallback')) + call padre#socket#Send({"cmd": "run"}, function('padre#debugger#GenericCallback')) endfunction function! padre#debugger#Stop() @@ -161,23 +161,33 @@ function! padre#debugger#Stop() endfunction function! s:SetBreakpointInDebugger(line, file) - call padre#socket#Send({"cmd": "breakpoint", "file": a:file, "line": str2nr(a:line)}, function('padre#debugger#BreakpointCallback')) + call padre#socket#Send({"cmd": "breakpoint", "file": a:file, "line": str2nr(a:line)}, function('padre#debugger#GenericCallback')) endfunction function! padre#debugger#Breakpoint() let l:breakpointAdded = padre#signs#ToggleBreakpoint() - if !empty(l:breakpointAdded) && padre#socket#Status() is# "open" - call s:SetBreakpointInDebugger(l:breakpointAdded['line'], l:breakpointAdded['file']) + if padre#socket#Status() is# "open" + if !empty(l:breakpointAdded) + call s:SetBreakpointInDebugger(l:breakpointAdded['line'], l:breakpointAdded['file']) + else + let l:file = expand('%') + let l:line = getpos('.')[1] + call padre#socket#Send({"cmd": "unbreakpoint", "file": l:file, "line": getpos('.')[1]}, function('padre#debugger#GenericCallback')) + endif endif endfunction -function! padre#debugger#StepIn() - call padre#socket#Send({"cmd": "stepIn"}, function('padre#debugger#StepInCallback')) +function! padre#debugger#StepIn(count) + call padre#socket#Send({"cmd": "stepIn", "count": a:count}, function('padre#debugger#GenericCallback')) endfunction -function! padre#debugger#StepOver() - call padre#socket#Send({"cmd": "stepOver"}, function('padre#debugger#StepOverCallback')) +function! padre#debugger#StepOver(count) + call padre#socket#Send({"cmd": "stepOver", "count": a:count}, function('padre#debugger#GenericCallback')) +endfunction + +function! padre#debugger#StepOut() + call padre#socket#Send({"cmd": "stepOut"}, function('padre#debugger#GenericCallback')) endfunction function! padre#debugger#PrintVariable(variable) @@ -185,7 +195,7 @@ function! padre#debugger#PrintVariable(variable) endfunction function! padre#debugger#Continue() - call padre#socket#Send({"cmd": "continue"}, function('padre#debugger#ContinueCallback')) + call padre#socket#Send({"cmd": "continue"}, function('padre#debugger#GenericCallback')) endfunction """""""""""""""" @@ -198,63 +208,12 @@ function! padre#debugger#SignalPADREStarted() endfor endfunction -function! padre#debugger#RunCallback(channel_id, data) +function! padre#debugger#GenericCallback(channel_id, data) if a:data['status'] != 'OK' call padre#debugger#Log(2, 'Error: ' . string(a:data)) - return - endif - - if has_key(a:data, 'pid') - call padre#debugger#Log(4, 'Process ' . a:data['pid'] . ' Running') endif endfunction -function! padre#debugger#BreakpointCallback(channel_id, data) - if a:data['status'] == 'OK' - elseif a:data['status'] == 'PENDING' - call padre#debugger#Log(4, 'Breakpoint pending') - else - call padre#debugger#Log(2, 'Error: ' . string(a:data)) - endif -endfunction - -function! padre#debugger#BreakpointSet(fileName, lineNum) - let l:msg = 'Breakpoint set file=' . a:fileName . ', line=' . a:lineNum - call padre#debugger#Log(4, l:msg) -endfunction - -function! padre#debugger#StepInCallback(channel_id, data) - if a:data['status'] != 'OK' - call padre#debugger#Log(2, 'Error: ' . string(a:data)) - endif -endfunction - -function! padre#debugger#StepOverCallback(channel_id, data) - if a:data['status'] != 'OK' - call padre#debugger#Log(2, 'Error: ' . string(a:data)) - endif -endfunction - -function! padre#debugger#ContinueCallback(channel_id, data) - if a:data['status'] != 'OK' - call padre#debugger#Log(2, 'Error: ' . string(a:data)) - endif -endfunction - -function! padre#debugger#PrintVariableCallback(channel_id, data) - let l:status = remove(a:data, 'status') - if l:status != 'OK' - call padre#debugger#Log(2, 'Error printing variable: ' . string(a:data)) - return - endif - - let l:variable_name = remove(a:data, 'variable') - - execute "let l:json = system('python -m json.tool', '" . substitute(json_encode(a:data), "'", "''", "g") . "')" - let l:msg = 'Variable ' . l:variable_name . "=\n" . l:json - call padre#debugger#Log(4, l:msg) -endfunction - function! padre#debugger#JumpToPosition(file, line) let l:msg = 'Stopped file=' . a:file . ' line=' . a:line call padre#debugger#Log(4, l:msg) @@ -304,8 +263,35 @@ function! padre#debugger#JumpToPosition(file, line) redraw endfunction -function! padre#debugger#ProcessExited(exit_code, pid) - call padre#debugger#Log(4, 'Process ' . a:pid . ' finished with exit code=' . a:exit_code) +function! padre#debugger#PrintVariableCallback(channel_id, data) + if a:data['status'] != 'OK' + call padre#debugger#Log(2, 'Error: ' . string(a:data)) + endif + + let s:text = 'Variable (' . a:data['type'] . ') ' . a:data['variable'] . '=' . a:data['value'] + + let l:current_tabpagenr = tabpagenr() + + call padre#layout#OpenTabWithBuffer('PADRE_Logs_' . s:PadreNumber) + + let l:current_window = winnr() + let l:logs_window = padre#layout#GetLogsWindow() + + if l:current_window != l:logs_window + execute l:logs_window . ' wincmd w' + endif + + call padre#buffer#AppendBuffer(strftime('%y/%m/%d %H:%M:%S ') . s:text, 0) + + if l:current_window != l:logs_window + execute l:current_window . ' wincmd w' + endif + + if l:current_tabpagenr != tabpagenr() + execute l:current_tabpagenr . 'tabnext' + endif + + redraw endfunction function! padre#debugger#Log(level, text) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index da78b76..11ef8b2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,13 +3,13 @@ trigger: - feature/* jobs: -- job: rustfmt +- job: rust_static pool: vmImage: 'ubuntu-latest' timeoutInMinutes: 5 steps: - - template: ci/rustfmt.yml + - template: ci/rust-static.yml - job: padre_tests pool: diff --git a/ci/install-rust.yml b/ci/install-rust.yml index 8504c12..4274253 100644 --- a/ci/install-rust.yml +++ b/ci/install-rust.yml @@ -2,7 +2,8 @@ steps: - script: | set -e curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable - echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" + echo "##vso[task.setvariable variable=PATH;]$HOME/.cargo/bin:$PATH" + rustup update displayName: Install rust - script: | diff --git a/ci/padre-tests.yml b/ci/padre-tests.yml index 6c615c0..3ef5532 100644 --- a/ci/padre-tests.yml +++ b/ci/padre-tests.yml @@ -2,37 +2,16 @@ steps: - template: install-rust.yml - script: | - cargo install cargo-junit - displayName: Install test reporters - - - script: | - sudo apt-get install software-properties-common python3 python3-pip python3-setuptools - pip3 install wheel - pip3 install behave pyhamcrest psutil - displayName: Install python dependencies - - - script: | - cd padre - cargo test -- --nocapture - env: - RUST_BACKTRACE: 1 - RUSTFLAGS: '-D warnings' - displayName: Run Padre CLI unit testing - - - script: | - cd padre - cargo junit --name TESTS-cargo.xml - displayName: Run Padre CLI unit testing reporting - - - script: | - sudo apt-get install -y lldb-5.0 gcc + sudo apt-get install -y software-properties-common python3 python3-pip python3-setuptools lldb-5.0 gcc sudo ln -s /usr/bin/lldb-5.0 /usr/bin/lldb sudo ln -s /usr/bin/lldb-server-5.0 /usr/bin/lldb-server sudo ln -s /usr/bin/lldb-server-5.0 /usr/lib/llvm-5.0/bin/lldb-server-5.0.0 sudo rm /usr/local/bin/node curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - sudo apt-get install -y nodejs - displayName: Install lldb-5, node-12 and build essentials + pip3 install wheel + pip3 install behave pyhamcrest psutil + displayName: Install lldb-5, node-12 and build and test essentials - script: | cd padre diff --git a/ci/rust-static.yml b/ci/rust-static.yml new file mode 100644 index 0000000..b811d1f --- /dev/null +++ b/ci/rust-static.yml @@ -0,0 +1,27 @@ +steps: + - template: install-rust.yml + + - script: | + cd padre + cargo fmt --all -- --check + displayName: Check formatting + + - script: | + rustup component add rustfmt + displayName: Install rustfmt + + - script: | + cargo install cargo-junit + displayName: Install test reporters + + - script: | + cd padre + cargo test -- --nocapture + env: + RUST_BACKTRACE: 1 + displayName: Run Padre CLI unit testing + + - script: | + cd padre + cargo junit --name TESTS-cargo.xml + displayName: Run Padre CLI unit testing reporting diff --git a/ci/rustfmt.yml b/ci/rustfmt.yml deleted file mode 100644 index 7e887ec..0000000 --- a/ci/rustfmt.yml +++ /dev/null @@ -1,11 +0,0 @@ -steps: - - template: install-rust.yml - - - script: | - rustup component add rustfmt - displayName: Install rustfmt - - - script: | - cd padre - cargo fmt --all -- --check - displayName: Check formatting diff --git a/padre/Cargo.lock b/padre/Cargo.lock index ee9f9ab..62deb88 100644 --- a/padre/Cargo.lock +++ b/padre/Cargo.lock @@ -2,1415 +2,581 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.6" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" dependencies = [ - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr", ] [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "arc-swap" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "arrayvec" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] name = "atty" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi", + "libc", + "winapi", ] [[package]] name = "autocfg" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64" -version = "0.10.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "byteorder" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "c2-chacha" -version = "0.2.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "ad1f8e949d755f9d79112b5bb46938e0ef9d3804a0b16dfab13aafcaa5f0fa72" [[package]] -name = "cc" -version = "1.0.40" +name = "cfg-if" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" -version = "0.1.9" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-foundation-sys" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "crossbeam-deque" -version = "0.7.1" +version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", ] [[package]] -name = "crossbeam-epoch" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" +name = "futures" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "foreign-types" -version = "0.3.2" +name = "futures-channel" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ - "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-sink", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "futures-core" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "futures-executor" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures" -version = "0.1.28" +name = "futures-io" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] -name = "getrandom" -version = "0.1.11" +name = "futures-macro" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "httparse" -version = "1.3.4" +name = "futures-sink" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" [[package]] -name = "hyper" -version = "0.10.16" +name = "futures-task" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ - "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell", ] [[package]] -name = "idna" -version = "0.1.5" +name = "futures-util" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", ] [[package]] -name = "iovec" -version = "0.1.2" +name = "hermit-abi" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "language-tags" -version = "0.2.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "lazy_static" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.62" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lock_api" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "log" -version = "0.3.9" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "memchr" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "memoffset" -version = "0.5.1" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "mio" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-named-pipes" -version = "0.1.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" dependencies = [ - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mio-uds" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", + "log", + "miow", + "ntapi", + "winapi", ] [[package]] name = "miow" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "miow" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "native-tls" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", - "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "net2" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "nodrop" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "num_cpus" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "openssl" -version = "0.10.24" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2", + "winapi", ] [[package]] -name = "openssl-probe" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "openssl-sys" -version = "0.9.49" +name = "ntapi" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi", ] [[package]] -name = "owning_ref" -version = "0.4.0" +name = "once_cell" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "padre" -version = "0.1.0" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-process 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "websocket 0.22.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "parking_lot_core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.0" dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "clap", + "futures", + "padre_core", + "padre_lldb", + "padre_python", + "serde", + "serde_derive", + "serde_json", + "tokio", + "tokio-util", ] [[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "pkg-config" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ppv-lite86" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "proc-macro2" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "padre_core" +version = "0.2.0" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "futures", + "serde", + "serde_derive", + "serde_json", + "tokio", + "tokio-util", ] [[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "padre_lldb" +version = "0.2.0" dependencies = [ - "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "futures", + "lazy_static", + "padre_core", + "regex", + "serde", + "serde_derive", + "serde_json", + "tokio", + "tokio-util", ] [[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" +name = "padre_python" +version = "0.2.0" dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "futures", + "lazy_static", + "padre_core", + "regex", + "serde", + "serde_derive", + "serde_json", + "tokio", + "tokio-util", ] [[package]] -name = "rand" -version = "0.7.0" +name = "pin-project" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a83804639aad6ba65345661744708855f9fbcb71176ea8d28d05aeb11d975e7" dependencies = [ - "getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-internal", ] [[package]] -name = "rand_chacha" -version = "0.1.1" +name = "pin-project-internal" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bcc46b8f73443d15bc1c5fecbb315718491fa9187fa483f0e359323cde8b3a" dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "rand_chacha" +name = "pin-project-lite" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "rand_core" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8" [[package]] -name = "rand_hc" +name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "rand_isaac" -version = "0.1.1" +name = "proc-macro-hack" +version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_os" -version = "0.1.3" +name = "proc-macro-nested" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" +name = "proc-macro2" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] -name = "rdrand" -version = "0.4.0" +name = "quote" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "regex" -version = "1.2.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" dependencies = [ - "aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "remove_dir_all" -version = "0.5.2" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", -] +checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" [[package]] name = "ryu" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "safemem" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "schannel" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "scopeguard" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "scopeguard" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "security-framework" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "security-framework-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" -version = "1.0.99" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" [[package]] name = "serde_derive" -version = "1.0.99" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ - "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "signal-hook" -version = "0.1.10" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa", + "ryu", + "serde", ] [[package]] name = "signal-hook-registry" -version = "1.1.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" dependencies = [ - "arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "smallvec" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "socket2" -version = "0.3.11" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", + "libc", + "winapi", ] -[[package]] -name = "stable_deref_trait" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "syn" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tempfile" -version = "3.1.0" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width", ] [[package]] name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "time" -version = "0.1.42" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static", ] [[package]] name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-codec" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-executor" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-fs" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-io" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-process" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-signal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-sync" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-timer" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-tls" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-udp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "tokio-uds" -version = "0.2.5" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258221f566b6c803c7b4714abadc080172b272090cdc5e244a6d4dd13c3a6bd" dependencies = [ - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", - "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "once_cell", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", ] [[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "unicase" -version = "1.4.2" +name = "tokio-macros" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" dependencies = [ - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unicode-bidi" -version = "0.3.4" +name = "tokio-stream" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4cdeb73537e63f98adcd73138af75e3f368ccaecffaa29d7eb61b9f5a440457" dependencies = [ - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] -name = "unicode-normalization" -version = "0.1.8" +name = "tokio-util" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36135b7e7da911f5f8b9331209f7fab4cc13498f3fff52f72a710c78187e3148" dependencies = [ - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", + "tokio-stream", ] [[package]] name = "unicode-width" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "vcpkg" -version = "0.2.7" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "wasi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "websocket" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)", - "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tokio-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi" -version = "0.2.8" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "winapi" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "854ede29f7a0ce90519fb2439d030320c6201119b87dab0ee96044603e1130b9" -"checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" -"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" -"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" -"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" -"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" -"checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" -"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" -"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" -"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" -"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" -"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" -"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" -"checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869" -"checksum getrandom 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "fc344b02d3868feb131e8b5fe2b9b0a1cc42942679af493061fc13b853243872" -"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" -"checksum hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" -"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" -"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" -"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" -"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" -"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" -"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" -"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" -"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" -"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" -"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" -"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" -"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" -"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" -"checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15" -"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884" -"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" -"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" -"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" -"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" -"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" -"checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" -"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -"checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" -"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -"checksum regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88c3d9193984285d544df4a30c23a4e62ead42edf70a4452ceb76dac1ce05c26" -"checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f" -"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" -"checksum safemem 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b08423011dae9a5ca23f07cf57dac3857f5c885d352b76f6d95f4aea9434d0" -"checksum schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f6abf258d99c3c1c5c2131d99d064e94b7b3dd5f416483057f308fea253339" -"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" -"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" -"checksum security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee63d0f4a9ec776eeb30e220f0bc1e092c3ad744b2a379e3993070364d3adc2" -"checksum security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9636f8989cbf61385ae4824b98c1aaa54c994d7d8b41f11c601ed799f0549a56" -"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" -"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" -"checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" -"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" -"checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68" -"checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc" -"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" -"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" -"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" -"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum syn 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c65d951ab12d976b61a41cf9ed4531fc19735c6e6d84a4bb1453711e762ec731" -"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -"checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" -"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -"checksum tokio-executor 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" -"checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" -"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" -"checksum tokio-process 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afbd6ef1b8cc2bd2c2b580d882774d443ebb1c6ceefe35ba9ea4ab586c89dbe8" -"checksum tokio-reactor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6af16bfac7e112bea8b0442542161bfc41cbfa4466b580bdda7d18cb88b911ce" -"checksum tokio-signal 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "dd6dc5276ea05ce379a16de90083ec80836440d5ef8a6a39545a3207373b8296" -"checksum tokio-sync 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2162248ff317e2bc713b261f242b69dbb838b85248ed20bb21df56d60ea4cae7" -"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -"checksum tokio-threadpool 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "90ca01319dea1e376a001e8dc192d42ebde6dd532532a5bad988ac37db365b19" -"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" -"checksum tokio-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c" -"checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" -"checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" -"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" -"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" -"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -"checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" -"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" -"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" -"checksum wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd5442abcac6525a045cc8c795aedb60da7a2e5e89c7bf18a0d5357849bb23c7" -"checksum websocket 0.22.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0adcd2a64c5746c9702b354a1b992802b0c363df1dfa324a74cb7aebe10e0cbf" -"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" -"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/padre/Cargo.toml b/padre/Cargo.toml index 52a2049..cbd245a 100644 --- a/padre/Cargo.toml +++ b/padre/Cargo.toml @@ -1,18 +1,5 @@ -[package] -name = "padre" -version = "0.1.0" -authors = ["Steven Trotter "] -edition = "2018" - -[dependencies] -bytes = "0.4.12" -clap = "2.32.0" -lazy_static = "1.3.0" -regex = "1.1.2" -serde = "1.0" -serde_derive = "1.0" -serde_json = "1.0" -tokio = "0.1.22" -tokio-signal = "0.2" -tokio-process = "0.2.4" -websocket = "0.22.4" +[workspace] +members = [ + "cli", + "core", +] diff --git a/padre/cli/Cargo.toml b/padre/cli/Cargo.toml new file mode 100644 index 0000000..94c29c9 --- /dev/null +++ b/padre/cli/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "padre" +version = "0.2.0" +authors = ["Steven Trotter "] +edition = "2018" +license = "APACHE" +repository = "https://github.com/strottos/vim-padre" + +[features] +default = ["lldb", "python"] +lldb = ["padre_lldb"] +python = ["padre_python"] + +[dependencies] +bytes = "1.0" +clap = "2.33.3" +futures = "0.3.8" +padre_core = { path = "../core", version = "0.2.0" } +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +tokio = { version = "1.0", features = [ + "io-std", + "io-util", + "macros", + "net", + "process", + "rt", + "signal", + "sync", + "time", +] } +tokio-util = { version = "0.6", features = ["codec"] } + +# The debuggers, all optional +padre_lldb = { path = "../debuggers/lldb", optional = true, version = "0.2.0" } +padre_python = { path = "../debuggers/python", optional = true, version = "0.2.0" } diff --git a/padre/src/config.rs b/padre/cli/src/config.rs similarity index 86% rename from padre/src/config.rs rename to padre/cli/src/config.rs index 5f7661f..c4c4349 100644 --- a/padre/src/config.rs +++ b/padre/cli/src/config.rs @@ -2,19 +2,18 @@ //! //! Responsible for reading and setting config items and setting default configs //! -//! The following config items can be set: +//! The following config items can be set (some are currently not yet supported, and some are +//! just ignored by debuggers when it makes sense): //! - BackPressure: Set the backpressure of the queue to build up. 0 means it errors on any //! request when one is in progress. Defaults to 20. //! - UnknownPosition: Set to the following: //! 0: if we should halt when we reach an unknown position //! 1: if we should carry on stepping over until we reach a known position. //! 2: if we should carry on stepping in until we reach a known position. -//! - ProcessSpawnTimeout: Set the timeout value for spawniong a process. Defaults -//! to 10 seconds. -//! - BreakpointTimeout: Timeout for setting a breakpoint. Defaults to 2 second. -//! Only used in LLDB. -//! - PrintVariableTimeout: Timeout for setting a breakpoint. Defaults to 2 second. -//! Only used in LLDB. +//! - ProcessSpawnTimeout: Set the timeout value for spawniong a process. +//! - BreakpointTimeout: Timeout for setting a breakpoint. +//! - StepTimeout: Timeout for stepping in code. +//! - PrintVariableTimeout: Timeout for printing a variable. use std::collections::HashMap; @@ -25,6 +24,7 @@ use std::collections::HashMap; /// /// Only config items that are meaningful and have defaults can be set and /// retreived. +#[derive(Debug)] pub struct Config<'a> { config: HashMap<&'a str, i64>, } @@ -36,6 +36,7 @@ impl<'a> Config<'a> { config.insert("UnknownPosition", 0); config.insert("ProcessSpawnTimeout", 10); config.insert("BreakpointTimeout", 2); + config.insert("StepTimeout", 2); config.insert("PrintVariableTimeout", 2); Config { config } } diff --git a/padre/cli/src/debugger.rs b/padre/cli/src/debugger.rs new file mode 100644 index 0000000..9b559ee --- /dev/null +++ b/padre/cli/src/debugger.rs @@ -0,0 +1,281 @@ +//! Handle creating the debugger dependent on the type of debugger specified +//! +//! See core/debugger.rs for more centric/shared debugger material once one is created + +use std::time::Instant; + +use tokio::process::Command; +use tokio::sync::{mpsc, oneshot}; + +use padre_core::debugger::DebuggerCmd; +use padre_core::server::Notification; +use padre_core::Result; + +#[cfg(feature = "lldb")] +use padre_lldb; +#[cfg(feature = "node")] +use padre_node; +#[cfg(feature = "python")] +use padre_python; + +/// Debuggers +#[derive(Debug)] +pub enum DebuggerType { + #[cfg(feature = "lldb")] + LLDB, + #[cfg(feature = "node")] + Node, + #[cfg(feature = "python")] + Python, +} + +#[derive(Debug)] +pub struct Debugger { + debugger_type: DebuggerType, + notifier_tx: mpsc::Sender, +} + +impl Debugger { + /// Get the debugger implementation + /// + /// If the debugger type is not specified it will try it's best to guess what kind of debugger to + /// return. + pub fn new(debugger_type: DebuggerType, notifier_tx: mpsc::Sender) -> Self { + Debugger { + debugger_type, + notifier_tx, + } + } + + pub async fn run( + &mut self, + debugger_cmd: String, + run_cmd: Vec, + debugger_queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + stop_rx: oneshot::Receiver<()>, + stop_tx: oneshot::Sender<()>, + ) { + match self.debugger_type { + #[cfg(feature = "lldb")] + DebuggerType::LLDB => { + let mut debugger = padre_lldb::get_debugger( + debugger_cmd, + run_cmd, + debugger_queue_rx, + self.notifier_tx.clone(), + ); + + tokio::select! { + _ = debugger.start() => {} + _ = stop_rx => { + debugger.stop().await; + + stop_tx.send(()).unwrap(); + } + }; + } + #[cfg(feature = "node")] + DebuggerType::Node => { + "node".to_string(); + } + #[cfg(feature = "python")] + DebuggerType::Python => { + let mut debugger = padre_python::get_debugger( + debugger_cmd, + run_cmd, + debugger_queue_rx, + self.notifier_tx.clone(), + ); + + tokio::select! { + _ = debugger.start() => {} + _ = stop_rx => { + debugger.stop().await; + + stop_tx.send(()).unwrap(); + } + }; + } + }; + } +} + +/// Guesses the debugger type and the debugger command if not specified +pub async fn get_debugger_info( + debugger_cmd: Option<&str>, + debugger_type: Option<&str>, + run_cmd: &Vec, +) -> (DebuggerType, String) { + let debugger_type = get_debugger_type(debugger_cmd, debugger_type, run_cmd).await; + let debugger_command = get_debugger_command(debugger_cmd, &debugger_type).await; + + (debugger_type, debugger_command) +} + +async fn get_debugger_type( + debugger_cmd: Option<&str>, + debugger_type: Option<&str>, + run_cmd: &Vec, +) -> DebuggerType { + match debugger_type { + Some(s) => match s.to_ascii_lowercase().as_str() { + #[cfg(feature = "lldb")] + "lldb" => DebuggerType::LLDB, + #[cfg(feature = "node")] + "node" => DebuggerType::Node, + #[cfg(feature = "python")] + "python" => DebuggerType::Python, + _ => panic!("Couldn't understand debugger type {}", s), + }, + None => { + #[cfg(feature = "node")] + if is_node(&run_cmd[0]).await { + return DebuggerType::Node; + } + + #[cfg(feature = "python")] + if is_python(&run_cmd[0]).await { + return DebuggerType::Python; + } + + #[cfg(feature = "lldb")] + if is_lldb(&run_cmd[0]).await { + return DebuggerType::LLDB; + } + + match debugger_cmd { + Some(s) => match s { + #[cfg(feature = "lldb")] + "lldb" => DebuggerType::LLDB, + #[cfg(feature = "node")] + "node" => DebuggerType::Node, + #[cfg(feature = "python")] + "python" | "python3" => DebuggerType::Python, + _ => panic!( + "Can't find debugger type for {}, try specifying with -d or -t", + s + ), + }, + None => panic!("Can't find debugger type, try specifying with -d or -t"), + } + } + } +} + +async fn get_debugger_command(debugger_cmd: Option<&str>, debugger_type: &DebuggerType) -> String { + match debugger_cmd { + Some(s) => s.to_string(), + None => match debugger_type { + #[cfg(feature = "lldb")] + DebuggerType::LLDB => "lldb".to_string(), + #[cfg(feature = "node")] + DebuggerType::Node => "node".to_string(), + #[cfg(feature = "python")] + DebuggerType::Python => "python3".to_string(), + }, + } +} + +/// Checks if the file is a binary executable +#[cfg(feature = "lldb")] +async fn is_lldb(cmd: &str) -> bool { + if file_is_binary_executable(cmd).await { + return true; + } + + false +} + +// /// Checks if the file is a NodeJS script +// #[cfg(feature = "node")] +// async fn is_node(cmd: &str) -> bool { +// if file_is_text(cmd).await && cmd.ends_with(".js") { +// return true; +// } +// +// // if file_is_binary_executable(cmd) && cmd.contains("node") { +// // return true; +// // } +// +// false +// } + +/// Checks if the file is a Python script +#[cfg(feature = "python")] +async fn is_python(cmd: &str) -> bool { + if file_is_text(cmd).await && cmd.ends_with(".py") { + return true; + } + + // if file_is_binary_executable(cmd) && cmd.contains("python") { + // return true; + // } + + false +} + +/// Find out if a file is a binary executable (either ELF or Mach-O +/// executable). +async fn file_is_binary_executable(cmd: &str) -> bool { + let output = get_file_type(cmd).await; + + if output.contains("ELF") + || (output.contains("Mach-O") && output.to_ascii_lowercase().contains("executable")) + { + true + } else { + false + } +} + +/// Get the file type as output by the UNIX `file` command. +async fn get_file_type(cmd: &str) -> String { + let output = Command::new("file") + .arg("-L") // Follow symlinks + .arg(cmd) + .output(); + let output = output + .await + .expect(&format!("Can't run file on {} to find file type", cmd)); + + String::from_utf8_lossy(&output.stdout).to_string() +} + +/// Find out if a file is a text file (either ASCII or UTF-8). +async fn file_is_text(cmd: &str) -> bool { + let output = get_file_type(cmd).await; + + if output.contains("ASCII") || output.contains("UTF-8") { + true + } else { + false + } +} + +#[cfg(test)] +mod tests { + #[tokio::test] + async fn is_file_executable() { + assert_eq!( + true, + super::file_is_binary_executable("../test_files/node").await + ); + assert_eq!( + false, + super::file_is_binary_executable("../test_files/test_node.js").await + ); + } + + #[tokio::test] + async fn is_file_text() { + assert_eq!(false, super::file_is_text("../test_files/node").await); + assert_eq!( + true, + super::file_is_text("../test_files/test_node.js").await + ); + } +} diff --git a/padre/cli/src/main.rs b/padre/cli/src/main.rs new file mode 100644 index 0000000..76971f4 --- /dev/null +++ b/padre/cli/src/main.rs @@ -0,0 +1,108 @@ +//! PADRE Debugger +//! +//! This program creates a socket interface that enables debuggers to communicate +//! in a standard manner with multiple different debuggers and programming languages. +//! Options supported: +//! -p/--port Port to run socket interface on +//! -h/--host Hostname to run on +//! -t/--type The type of debugger to spawn +//! Currently supported are +//! - lldb +//! - node +//! - python +//! -d/--debugger +//! +//! The debug command should be specified as an addendum when running the command, e.g. +//! ``` +//! padre -t=lldb -d=lldb -- my_program arg1 arg2 3 4 +//! ``` +//! will run the program `my_program arg1 arg2 3 4` in an `lldb` session. + +use std::net::SocketAddr; + +use clap::{App, Arg, ArgMatches}; +use tokio::runtime; + +use padre_core::util::get_unused_localhost_port; + +mod config; +mod debugger; +mod server; +mod vimcodec; + +fn get_app_args<'a>() -> ArgMatches<'a> { + App::new("VIM Padre") + .version("0.1.0") + .author("Steven Trotter ") + .about("A tool for building, debugging and reverse engineering in VIM") + .long_about("Interfaces with 'lldb' or a similar debugger to debug programs and communicate with the Vim PADRE plugin in order to effectively use Vim as a debugging interface.") + .arg(Arg::with_name("port") + .short("p") + .long("port") + .takes_value(true) + .help("specify port to run on")) + .arg(Arg::with_name("host") + .short("h") + .long("host") + .takes_value(true) + .help("specify host to run on")) + .arg(Arg::with_name("debugger") + .short("d") + .long("debugger") + .takes_value(true) + .help("specify debugger to use")) + .arg(Arg::with_name("type") + .short("t") + .long("type") + .takes_value(true) + .help("specify debugger type from [lldb, node, python]")) + .arg(Arg::with_name("debug_cmd") + .multiple(true) + .takes_value(true)) + .get_matches() +} + +fn get_connection(args: &ArgMatches) -> SocketAddr { + let port = match args.value_of("port") { + None => get_unused_localhost_port(), + Some(s) => match s.parse::() { + Ok(n) => n, + Err(_) => { + panic!("Can't understand port"); + } + }, + }; + + let host = match args.value_of("host") { + None => "0.0.0.0", + Some(s) => s, + }; + + return format!("{}:{}", host, port).parse::().unwrap(); +} + +fn main() { + let rt = runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(async { + let args = get_app_args(); + + let debugger = args.value_of("debugger"); + let debugger_type = args.value_of("type"); + let debug_cmd: Vec = args + .values_of("debug_cmd") + .expect("Can't find program to debug, please rerun with correct parameters") + .map(|x| x.to_string()) + .collect::>(); + + let connection_addr = get_connection(&args); + + match server::run(connection_addr, debugger, debugger_type, debug_cmd).await { + Ok(_) => {} + Err(e) => panic!("PADRE error: {:?}", e), + } + }); +} diff --git a/padre/cli/src/server.rs b/padre/cli/src/server.rs new file mode 100644 index 0000000..c2714f3 --- /dev/null +++ b/padre/cli/src/server.rs @@ -0,0 +1,503 @@ +//! Handle a connection to the PADRE server including passing messages to and +//! from the debugger to the connection. + +use std::env::current_exe; +use std::io; +use std::net::SocketAddr; +use std::process::{exit, Command, Stdio}; +use std::str; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use futures::prelude::*; +use tokio::net::{TcpListener, TcpStream}; +use tokio::signal; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio::sync::oneshot; +use tokio::time::timeout; +use tokio_util::codec::Decoder; + +use crate::config::Config; +use crate::debugger::{get_debugger_info, Debugger}; +use crate::vimcodec::VimCodec; +use padre_core::debugger::DebuggerCmd; +use padre_core::server::{LogLevel, Notification}; +use padre_core::util::{log_msg, serde_json_merge}; +use padre_core::Result; + +/// Contains command details of a request, either a `PadreCmd` or a `DebuggerCmd` +/// +/// Can be of the form of a command without arguments, a command with a location argument or a +/// command with a variable argument. +/// +/// Examples: +/// +/// ``` +/// let command = RequestCmd::PadreCmd(PadreCmd::Ping); +/// let command = RequestCmd::DebuggerCmd(DebuggerCmd::Breakpoint(FileLocation::new("test.c", 12))); +/// let command = RequestCmd::DebuggerCmd(DebuggerCmd::Print(Variable::new("abc"))); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub enum RequestCmd { + PadreCmd(PadreCmd), + // 2nd argument is timeout instant value + DebuggerCmd(DebuggerCmd), +} + +/// Contains full details of a request including an id to respond to and a `RequestCmd` +#[derive(Clone, Debug, PartialEq)] +pub struct PadreRequest { + id: u64, + cmd: RequestCmd, +} + +impl PadreRequest { + /// Create a request + pub fn new(id: u64, cmd: RequestCmd) -> Self { + PadreRequest { id, cmd } + } + + /// Return the request id + pub fn id(&self) -> u64 { + self.id + } + + /// Return the RequestCmd entry + pub fn cmd(&self) -> &RequestCmd { + &self.cmd + } +} + +/// All padre commands +#[derive(Clone, Debug, PartialEq)] +pub enum PadreCmd { + Ping, + Pings, + GetConfig(String), + SetConfig(String, i64), +} + +/// A response to a request +/// +/// Takes a u64 as the first argument to represent the id and a JSON object as +/// the second argument to represent the response. For example a response with an id of `1` +/// and a JSON object of `{"status":"OK"}` will be decoded by the `VIMCodec` as +/// `[1,{"status":"OK"}]` and sent as a response to the requesting socket. +/// +/// Normally kept simple with important information relegated to an event based notification. +#[derive(Clone, Debug, PartialEq)] +pub struct PadreResponse { + id: u64, + resp: serde_json::Value, +} + +impl PadreResponse { + /// Create a response + pub fn new(id: u64, resp: serde_json::Value) -> Self { + PadreResponse { id, resp } + } + + /// Return the response id + pub fn id(&self) -> u64 { + self.id + } + + /// Return the response values + pub fn resp(&self) -> &serde_json::Value { + &self.resp + } +} + +/// Data to be sent back to connection in the form of either a `Notification` or a `PadreResponse` +/// +/// A `PadreResponse` takes a u64 as the first argument to represent the id and a JSON object as +/// the second argument to represent the response. For example a response with an id of `1` +/// and a JSON object of `{"status":"OK"}` will be decoded by the `VIMCodec` as +/// `[1,{"status":"OK"}]` and sent as a response to the requesting socket. +/// +#[derive(Clone, Debug, PartialEq)] +pub enum PadreSend { + Response(PadreResponse), + Notification(Notification), +} + +pub async fn run( + connection_addr: SocketAddr, + debugger_cmd: Option<&str>, + debugger_type: Option<&str>, + run_cmd: Vec, +) -> std::result::Result<(), io::Error> { + let tcp_listener = TcpListener::bind(&connection_addr) + .map(|tcp_listener| { + println!("Listening on {}", &connection_addr); + tcp_listener + }) + .await + .expect(&format!("Can't open TCP listener on {}", &connection_addr)); + + let (notifier_tx, notifier_rx): (Sender, Receiver) = + mpsc::channel(1); + let (debugger_queue_tx, debugger_queue_rx) = mpsc::channel(1); + + let (debugger_type, debugger_cmd) = + get_debugger_info(debugger_cmd, debugger_type, &run_cmd).await; + + let (send_stop_tx, send_stop_rx) = oneshot::channel(); + let (recv_stop_tx, recv_stop_rx) = oneshot::channel(); + + let mut debugger = Debugger::new(debugger_type, notifier_tx.clone()); + tokio::spawn(async move { + debugger + .run( + debugger_cmd, + run_cmd, + debugger_queue_rx, + send_stop_rx, + recv_stop_tx, + ) + .await + }); + let mut server = Server::new( + &connection_addr, + tcp_listener, + notifier_tx, + debugger_queue_tx, + ); + + tokio::select! { + _ = server.run(notifier_rx) => {} + _ = signal::ctrl_c() => { + send_stop_tx.send(()).expect("Failed to send shutdown signal to debugger"); + + if let Err(_) = timeout(Duration::new(5, 0), recv_stop_rx).await { + println!("Timed out exiting!"); + exit(-1); + }; + + exit(0); + } + } + + Ok(()) +} + +pub struct Server<'a> { + addr: &'a SocketAddr, + tcp_listener: TcpListener, + listeners: Arc, SocketAddr)>>>, + notifier_tx: Sender, + debugger_queue_tx: Sender<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, +} + +impl<'a> Server<'a> { + pub fn new( + addr: &'a SocketAddr, + tcp_listener: TcpListener, + notifier_tx: Sender, + debugger_queue_tx: Sender<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + ) -> Self { + Server { + addr, + tcp_listener, + listeners: Arc::new(Mutex::new(vec![])), + notifier_tx, + debugger_queue_tx, + } + } + + /// Process a TCP listener. + pub async fn run(&mut self, mut notifier_rx: Receiver) { + let listeners = self.listeners.clone(); + + tokio::spawn(async move { + while let Some(msg) = notifier_rx.recv().await { + let mut listeners_lock = listeners.lock().unwrap(); + // Expected there's mostly only ever one in here unless debugging or something, but + // we support more. + for listener in listeners_lock.iter_mut() { + let sender = listener.0.clone(); + let msg_send = PadreSend::Notification(msg.clone()); + tokio::spawn(async move { + if let Err(_) = sender.send(msg_send).await { + // Just skip on, this shouldn't happen very often as we should have + // very few listeners + } + }); + } + } + }); + + loop { + let (socket, _) = self.tcp_listener.accept().await.unwrap(); + self.handle(socket).await; + } + } + + /// Process a TCP socket connection. + /// + /// Fully sets up a new socket connection including listening for requests and sending responses. + async fn handle(&mut self, stream: TcpStream) { + let config = Arc::new(Mutex::new(Config::new())); + + let connection_response = ConnectionResponse::new(self.notifier_tx.clone()); + + let (mut connection_tx, mut connection_rx) = + VimCodec::new(config.clone()).framed(stream).split(); + + // Just here as an abstraction around the connection so that we can send both responses + // to requests and to notify of an event (debugger paused for example). + let (tx, mut rx) = mpsc::channel(1); + + self.add_listener(tx.clone(), self.addr.clone()); + + tokio::spawn(async move { + while let Some(msg) = rx.recv().await { + connection_tx.send(msg).await.unwrap(); + } + }); + + let debugger_queue_tx = self.debugger_queue_tx.clone(); + + tokio::spawn(async move { + while let Some(req) = connection_rx.next().await { + match req { + Ok(req) => { + match connection_response.get_response(&req, debugger_queue_tx.clone(), config.clone()).await { + Ok(resp) => { + tx.clone().send(PadreSend::Response(resp)).await.unwrap() + }, + Err(e) => { + tx.clone().send(PadreSend::Response(PadreResponse::new( + req.id(), + serde_json::json!({"status":"ERROR","error":e.get_error_string(),"debug":e.get_debug_string()}), + ))).await.unwrap() + }, + }; + }, + Err(e) => { + tx.clone().send(PadreSend::Response(PadreResponse::new( + e.get_id(), + serde_json::json!({"status":"ERROR","error":e.get_error_string(),"debug":e.get_debug_string()}), + ))).await.unwrap() + } + } + } + }); + + self.check_for_and_report_padre_updates(); + } + + /// Add a socket as a listener that will be notified of any events that happen in debuggers + /// asynchronously like code stepping, etc. + fn add_listener(&mut self, sender: Sender, addr: SocketAddr) { + self.listeners.lock().unwrap().push((sender, addr)); + } + + // TODO + // /// Remove a listener from the notifier + // /// + // /// Should be called when a connection is dropped. + // fn remove_listener(&mut self, addr: &SocketAddr) { + // self.listeners + // .lock() + // .unwrap() + // .retain(|listener| listener.1 != *addr); + // } + + /// Checks whether we're on the latest version with git and if not gives a warning + fn check_for_and_report_padre_updates(&self) { + let padre_exe = current_exe().unwrap(); + let padre_dir = padre_exe.parent().unwrap(); + + // TODO: Assumes git is used for now and exists, add releasing option in later. + let output = Command::new("git") + .arg("status") + .current_dir(padre_dir) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute git command, can't tell if PADRE needs updating"); + + let status = str::from_utf8(&output.stdout) + .unwrap() + .split('\n') + .collect::>(); + + // TODO: Change + if *status.get(0).unwrap() == "On branch master" { + Command::new("git") + .args(vec!["remote", "update"]) + .current_dir(padre_dir) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute git command, can't tell if PADRE needs updating"); + + let output = Command::new("git") + .arg("status") + .current_dir(padre_dir) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .expect("Failed to execute git command, can't tell if PADRE needs updating"); + + let status = str::from_utf8(&output.stdout) + .unwrap() + .split('\n') + .collect::>(); + + if status.get(1).unwrap().starts_with("Your branch is behind ") { + println!("Your PADRE version is out of date and should be updated, please run `git pull` in your PADRE directory and and then rerun `make`."); + log_msg( + self.notifier_tx.clone(), + LogLevel::WARN, + "Your PADRE version is out of date and should be updated, please run `git pull` in your PADRE directory and and then rerun `make`." + ); + } + } + } +} + +struct ConnectionResponse { + notifier_tx: Sender, +} + +impl ConnectionResponse { + fn new(notifier_tx: Sender) -> Self { + ConnectionResponse { notifier_tx } + } + + /// Process a PadreRequest and figure out the response. + /// + /// Forwards the request to the appropriate place to handle it and responds appropriately. + async fn get_response<'a>( + &self, + request: &PadreRequest, + debugger_queue_tx: Sender<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + config: Arc>>, + ) -> Result { + match request.cmd() { + RequestCmd::PadreCmd(cmd) => { + let json_response = match cmd { + PadreCmd::Ping => self.ping(), + PadreCmd::Pings => self.pings(), + PadreCmd::GetConfig(key) => self.get_config(config, key), + PadreCmd::SetConfig(key, value) => self.set_config(config, key, *value), + }; + + match json_response { + Ok(args) => Ok(PadreResponse::new(request.id(), args)), + Err(e) => { + log_msg(self.notifier_tx.clone(), LogLevel::ERROR, &format!("{}", e)); + let resp = serde_json::json!({"status":"ERROR"}); + Ok(PadreResponse::new(request.id(), resp)) + } + } + } + RequestCmd::DebuggerCmd(cmd) => { + let (tx, rx) = oneshot::channel(); + + let config_timeout: u64 = match cmd { + DebuggerCmd::Breakpoint(_) | DebuggerCmd::Unbreakpoint(_) => config + .lock() + .unwrap() + .get_config("BreakpointTimeout") + .unwrap() + as u64, + DebuggerCmd::Run => config + .lock() + .unwrap() + .get_config("ProcessSpawnTimeout") + .unwrap() as u64, + DebuggerCmd::Print(_) => config + .lock() + .unwrap() + .get_config("PrintVariableTimeout") + .unwrap() as u64, + DebuggerCmd::StepIn(_) + | DebuggerCmd::StepOver(_) + | DebuggerCmd::StepOut + | DebuggerCmd::Continue => { + config.lock().unwrap().get_config("StepTimeout").unwrap() as u64 + } + }; + let timeout = Instant::now() + Duration::new(config_timeout, 0); + + match debugger_queue_tx.send((cmd.clone(), timeout, tx)).await { + Ok(_) => {} + Err(e) => { + return Ok(PadreResponse::new( + request.id(), + serde_json::json!({ + "status": "ERROR", + "error": "Error sending message to debugger", + "debug": format!("Error sending message to debugger: {}", e) + }), + )) + } + }; + + match rx.await { + Ok(msg) => match msg { + Ok(msg) => { + let mut ret = serde_json::json!({"status":"OK"}); + serde_json_merge(&mut ret, msg); + Ok(PadreResponse::new(request.id(), ret)) + } + Err(e) => Ok(PadreResponse::new( + request.id(), + serde_json::json!({"status":"ERROR","error":e.get_error_string(),"debug":e.get_debug_string()}), + )), + }, + Err(e) => Ok(PadreResponse::new( + request.id(), + serde_json::json!({"status":"ERROR","error":"Didn't recieve done notification","debug":&format!("{:?}", e)}), + )), + } + } + } + } + + fn ping(&self) -> Result { + Ok(serde_json::json!({"status":"OK","ping":"pong"})) + } + + fn pings(&self) -> Result { + log_msg(self.notifier_tx.clone(), LogLevel::INFO, "pong"); + + Ok(serde_json::json!({"status":"OK"})) + } + + fn get_config(&self, config: Arc>, key: &str) -> Result { + let value = config.lock().unwrap().get_config(key); + match value { + Some(v) => Ok(serde_json::json!({"status":"OK","value":v})), + None => Ok(serde_json::json!({"status":"ERROR"})), + } + } + + fn set_config( + &self, + config: Arc>, + key: &str, + value: i64, + ) -> Result { + let config_set = config.lock().unwrap().set_config(key, value); + match config_set { + true => Ok(serde_json::json!({"status":"OK"})), + false => Ok(serde_json::json!({"status":"ERROR"})), + } + } +} diff --git a/padre/cli/src/vimcodec.rs b/padre/cli/src/vimcodec.rs new file mode 100644 index 0000000..b72ed92 --- /dev/null +++ b/padre/cli/src/vimcodec.rs @@ -0,0 +1,688 @@ +//! VIMCodec +//! +//! Rust Tokio Codec for communicating with VIM + +use std::collections::HashMap; +use std::io; +use std::sync::{Arc, Mutex}; + +use crate::config::Config; +use crate::server::{PadreCmd, PadreRequest, PadreSend, RequestCmd}; +use padre_core::debugger::{DebuggerCmd, FileLocation, Variable}; +use padre_core::server::{PadreError, PadreErrorKind}; + +use bytes::{Buf, BufMut, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; + +#[derive(Debug)] +pub struct PadreErrorWithId { + padre_error: PadreError, + id: u64, +} + +impl PadreErrorWithId { + pub fn new(kind: PadreErrorKind, id: u64, error_string: String, debug_string: String) -> Self { + PadreErrorWithId { + id, + padre_error: PadreError::new(kind, error_string, debug_string), + } + } + + pub fn get_id(&self) -> u64 { + self.id + } + + pub fn get_error_string(&self) -> &str { + &self.padre_error.get_error_string() + } + + pub fn get_debug_string(&self) -> &str { + &self.padre_error.get_debug_string() + } +} + +impl From for PadreErrorWithId { + fn from(err: io::Error) -> PadreErrorWithId { + PadreErrorWithId::new( + PadreErrorKind::GenericError, + 0, + "Generic error".to_string(), + format!("Generic error {}", err), + ) + } +} + +type Result = std::result::Result; + +/// Decodes requests and encodes responses sent by or to VIM over VIM's socket communication +/// +/// Given a request of the form +/// ``` +/// [1,{"cmd":"breakpoint","file":"test.c","line":1}] +/// ``` +/// it decodes this into a PadreRequest with an `id` of `1` and a RequestCmd of `Breakpoint` +/// with the correct file location. +#[derive(Debug)] +pub struct VimCodec<'a> { + config: Arc>>, +} + +impl<'a> VimCodec<'a> { + /// Constructor for creating a new VimCodec + /// + /// Just creates the object at present. + pub fn new(config: Arc>>) -> Self { + VimCodec { config } + } + + /// Get and remove a `file location` from the arguments + fn get_file_location( + &self, + args: &mut HashMap, + id: u64, + ) -> Result { + match args.remove("file") { + Some(s) => match s { + serde_json::Value::String(s) => match args.remove("line") { + Some(t) => match t { + serde_json::Value::Number(t) => { + let t: u64 = match t.as_u64() { + Some(t) => t, + None => { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Badly specified 'line'".to_string(), + format!("Badly specified 'line': {}", t), + )); + } + }; + Ok(FileLocation::new(s, t)) + } + _ => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't read 'line' argument".to_string(), + format!("Can't understand 'line': {}", t), + )), + }, + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't understand request".to_string(), + "Need to specify a line number".to_string(), + )), + }, + _ => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't read 'file' argument".to_string(), + format!("Can't understand 'file': {}", s), + )), + }, + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't understand request".to_string(), + "Need to specify a file name".to_string(), + )), + } + } + + /// Get and remove a `variable` from the arguments passed + fn get_variable( + &self, + args: &mut HashMap, + id: u64, + ) -> Result { + match args.remove("variable") { + Some(s) => match s { + serde_json::Value::String(s) => Ok(Variable::new(s)), + _ => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Badly specified 'variable'".to_string(), + format!("Badly specified 'variable': {}", s), + )), + }, + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't understand request".to_string(), + "Need to specify a variable name".to_string(), + )), + } + } + + /// Get and remove the key specified from the arguments as a String + fn get_string( + &self, + key: &str, + args: &mut HashMap, + id: u64, + ) -> Result { + match args.remove(key) { + Some(s) => match s { + serde_json::Value::String(s) => Ok(s), + _ => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + format!("Badly specified string '{}'", key), + format!("Badly specified string '{}': {}", key, s), + )), + }, + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't understand request".to_string(), + format!("Need to specify a '{}'", key), + )), + } + } + + /// Get and remove the key specified from the arguments as an i64 + fn get_i64( + &self, + key: &str, + args: &mut HashMap, + id: u64, + ) -> Result { + match args.remove(key) { + Some(k) => match k.clone() { + serde_json::Value::Number(n) => match n.as_i64() { + Some(i) => Ok(i), + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + format!("Badly specified 64-bit integer '{}'", key), + format!("Badly specified 64-bit integer '{}': {}", key, &k), + )), + }, + _ => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + format!("Badly specified 64-bit integer '{}'", key), + format!("Badly specified 64-bit integer '{}': {}", key, &k), + )), + }, + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't understand request".to_string(), + format!("Need to specify a '{}'", key), + )), + } + } + + /// Get and remove the key specified from the arguments as a u64 + fn get_u64( + &self, + key: &str, + args: &mut HashMap, + id: u64, + ) -> Result { + match args.remove(key) { + Some(k) => match k.clone() { + serde_json::Value::Number(n) => match n.as_u64() { + Some(i) => Ok(i), + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + format!("Badly specified 64-bit unsigned integer '{}'", key), + format!("Badly specified 64-bit unsigned integer '{}': {}", key, &k), + )), + }, + _ => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + format!("Badly specified 64-bit unsigned integer '{}'", key), + format!("Badly specified 64-bit unsigned integer '{}': {}", key, &k), + )), + }, + None => Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't understand request".to_string(), + format!("Need to specify a '{}'", key), + )), + } + } +} + +impl<'a> Decoder for VimCodec<'a> { + type Item = PadreRequest; + type Error = PadreErrorWithId; + + fn decode(&mut self, src: &mut BytesMut) -> Result> { + if src.len() == 0 { + return Ok(None); + } + + let mut stream = serde_json::Deserializer::from_slice(src).into_iter::(); + let req = &src.clone()[..]; + + let mut v = match stream.next() { + Some(s) => match s { + Ok(t) => t, + Err(e) => { + match e.classify() { + serde_json::error::Category::Io => { + println!("IO: {:?}", req); + } + serde_json::error::Category::Syntax => {} + serde_json::error::Category::Data => { + println!("Data: {:?}", req); + } + serde_json::error::Category::Eof => { + return Ok(None); + } + }; + + src.advance(src.len()); + + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + 0, + "Must be valid JSON".to_string(), + format!( + "Can't read '{}': {}", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)), + e + ), + )); + } + }, + None => { + return Ok(None); + } + }; + + let offset = stream.byte_offset(); + src.advance(offset); + + if !v.is_array() { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + 0, + "Not an array, invalid JSON".to_string(), + format!( + "Can't read '{}': Must be an array", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)) + ), + )); + } + + if v.as_array().unwrap().len() == 0 { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + 0, + "Array must have 2 elements, invalid JSON".to_string(), + format!( + "Can't read '{}': Array should have 2 elements", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)) + ), + )); + } + + let id = v[0].take(); + let id: u64 = match serde_json::from_value(id.clone()) { + Ok(s) => s, + Err(e) => { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + 0, + "Can't read id".to_string(), + format!("Can't read '{}': {}", id, e), + )); + } + }; + + if v.as_array().unwrap().len() != 2 { + let e = PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Array must have 2 elements, invalid JSON".to_string(), + format!( + "Can't read '{}': Array should have 2 elements", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)) + ), + ); + return Err(e); + } + + let mut args: HashMap = + match serde_json::from_str(&v[1].take().to_string()) { + Ok(args) => args, + Err(e) => { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't read 2nd argument as dictionary".to_string(), + format!( + "Can't read '{}': {}", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)), + e + ), + )); + } + }; + + let cmd: String = match args.remove("cmd") { + Some(s) => match serde_json::from_value(s) { + Ok(s) => s, + Err(e) => { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't find command".to_string(), + format!( + "Can't find command '{}': {}", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)), + e + ), + )); + } + }, + None => { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Can't find command".to_string(), + format!( + "Can't find command '{}': Need a cmd in 2nd object", + String::from_utf8_lossy(&req[..]).trim_matches(char::from(0)) + ), + )); + } + }; + + let ret = match &cmd[..] { + "ping" => Ok(Some(PadreRequest::new( + id, + RequestCmd::PadreCmd(PadreCmd::Ping), + ))), + "pings" => Ok(Some(PadreRequest::new( + id, + RequestCmd::PadreCmd(PadreCmd::Pings), + ))), + "run" => Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::Run), + ))), + "stepOver" => { + let count = match self.get_u64("count", &mut args, id) { + Ok(c) => c, + Err(_) => 1, + }; + Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::StepOver(count)), + ))) + } + "stepIn" => { + let count = match self.get_u64("count", &mut args, id) { + Ok(c) => c, + Err(_) => 1, + }; + Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::StepIn(count)), + ))) + } + "stepOut" => Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::StepOut), + ))), + "continue" => Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::Continue), + ))), + "breakpoint" => { + let file_location = self.get_file_location(&mut args, id); + match file_location { + Ok(fl) => Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::Breakpoint(fl)), + ))), + Err(e) => return Err(e), + } + } + "unbreakpoint" => { + let file_location = self.get_file_location(&mut args, id); + match file_location { + Ok(fl) => Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::Unbreakpoint(fl)), + ))), + Err(e) => return Err(e), + } + } + "print" => { + let variable = self.get_variable(&mut args, id); + match variable { + Ok(v) => Ok(Some(PadreRequest::new( + id, + RequestCmd::DebuggerCmd(DebuggerCmd::Print(v)), + ))), + Err(e) => return Err(e), + } + } + "getConfig" => { + let key = self.get_string("key", &mut args, id); + match key { + Ok(k) => Ok(Some(PadreRequest::new( + id, + RequestCmd::PadreCmd(PadreCmd::GetConfig(k)), + ))), + Err(e) => return Err(e), + } + } + "setConfig" => { + let key = self.get_string("key", &mut args, id); + match key { + Ok(k) => { + let value = self.get_i64("value", &mut args, id); + match value { + Ok(v) => Ok(Some(PadreRequest::new( + id, + RequestCmd::PadreCmd(PadreCmd::SetConfig(k, v)), + ))), + Err(e) => return Err(e), + } + } + Err(e) => return Err(e), + } + } + _ => { + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Command unknown".to_string(), + format!("Command unknown: '{}'", cmd), + )); + } + }; + + match args.is_empty() { + true => {} + false => { + let mut args_left: Vec = args.iter().map(|(key, _)| key.clone()).collect(); + args_left.sort(); + return Err(PadreErrorWithId::new( + PadreErrorKind::RequestSyntaxError, + id, + "Bad arguments".to_string(), + format!("Bad arguments: {:?}", args_left), + )); + } + }; + + ret + } +} + +impl<'a> Encoder for VimCodec<'a> { + type Error = PadreError; + + fn encode(&mut self, resp: PadreSend, buf: &mut BytesMut) -> padre_core::Result<()> { + let response = match resp { + PadreSend::Response(resp) => { + serde_json::to_string(&(resp.id(), resp.resp())).unwrap() + "\n" + } + PadreSend::Notification(notification) => { + serde_json::to_string(&( + "call".to_string(), + notification.cmd(), + notification.args(), + )) + .unwrap() + + "\n" + } + }; + + buf.reserve(response.len()); + buf.put(response[..].as_bytes()); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::sync::{Arc, Mutex}; + + use crate::config::Config; + use crate::server::{PadreCmd, PadreResponse, PadreSend, RequestCmd}; + use padre_core::debugger::DebuggerCmd; + use padre_core::server::Notification; + + use bytes::{BufMut, BytesMut}; + use tokio_util::codec::{Decoder, Encoder}; + + #[test] + fn check_simple_json_decoding() { + let config = Arc::new(Mutex::new(Config::new())); + + let mut codec = super::VimCodec::new(config); + let mut buf = BytesMut::new(); + buf.reserve(19); + let s = r#"[123,{"cmd":"run"}]"#; + buf.put(s.as_bytes()); + + let padre_request = codec.decode(&mut buf).unwrap().unwrap(); + + assert_eq!(123, padre_request.id()); + + match padre_request.cmd() { + RequestCmd::DebuggerCmd(cmd) => { + assert_eq!(DebuggerCmd::Run, *cmd); + } + _ => panic!("Wrong command type"), + } + } + + #[test] + fn check_two_simple_json_decoding() { + let config = Arc::new(Mutex::new(Config::new())); + + let mut codec = super::VimCodec::new(config); + let mut buf = BytesMut::new(); + buf.reserve(19); + let s = r#"[123,{"cmd":"run"}]"#; + buf.put(s.as_bytes()); + + let padre_request = codec.decode(&mut buf).unwrap().unwrap(); + + assert_eq!(123, padre_request.id()); + + match padre_request.cmd() { + RequestCmd::DebuggerCmd(cmd) => { + assert_eq!(DebuggerCmd::Run, *cmd); + } + _ => panic!("Wrong command type"), + } + + let mut buf = BytesMut::new(); + buf.reserve(20); + let s = r#"[124,{"cmd":"ping"}]"#; + buf.put(s.as_bytes()); + + let padre_request = codec.decode(&mut buf).unwrap().unwrap(); + + assert_eq!(124, padre_request.id()); + + match padre_request.cmd() { + RequestCmd::PadreCmd(cmd) => { + assert_eq!(PadreCmd::Ping, *cmd); + } + _ => panic!("Wrong command type"), + } + } + + #[test] + fn check_two_buffers_json_decodings() { + let config = Arc::new(Mutex::new(Config::new())); + + let mut codec = super::VimCodec::new(config); + let mut buf = BytesMut::new(); + buf.reserve(16); + let s = r#"[123,{"cmd":"run"#; + buf.put(s.as_bytes()); + + let padre_request = codec.decode(&mut buf).unwrap(); + + assert_eq!(None, padre_request); + + buf.reserve(3); + let s = r#""}]"#; + buf.put(s.as_bytes()); + + let padre_request = codec.decode(&mut buf).unwrap().unwrap(); + + assert_eq!(123, padre_request.id()); + + match padre_request.cmd() { + RequestCmd::DebuggerCmd(cmd) => { + assert_eq!(DebuggerCmd::Run, *cmd); + } + _ => panic!("Wrong command type"), + } + } + + #[test] + fn check_json_encoding_response() { + let config = Arc::new(Mutex::new(Config::new())); + + let mut codec = super::VimCodec::new(config); + let resp = PadreSend::Response(PadreResponse::new(123, serde_json::json!({"ping":"pong"}))); + let mut buf = BytesMut::new(); + codec.encode(resp, &mut buf).unwrap(); + + let mut expected = BytesMut::new(); + expected.reserve(22); + let s = format!("{}{}", r#"[123,{"ping":"pong"}]"#, "\n"); + expected.put(s.as_bytes()); + + assert_eq!(expected, buf); + } + + #[test] + fn check_json_encoding_notify() { + let config = Arc::new(Mutex::new(Config::new())); + + let mut codec = super::VimCodec::new(config); + let resp = PadreSend::Notification(Notification::new( + "cmd_test".to_string(), + vec![serde_json::json!("test"), serde_json::json!(1)], + )); + let mut buf = BytesMut::new(); + codec.encode(resp, &mut buf).unwrap(); + + let mut expected = BytesMut::new(); + expected.reserve(31); + let s = format!("{}{}", r#"["call","cmd_test",["test",1]]"#, "\n"); + expected.put(s.as_bytes()); + + assert_eq!(expected, buf); + } +} diff --git a/padre/core/Cargo.toml b/padre/core/Cargo.toml new file mode 100644 index 0000000..af03235 --- /dev/null +++ b/padre/core/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "padre_core" +version = "0.2.0" +authors = ["Steven Trotter "] +edition = "2018" +license = "APACHE" +repository = "https://github.com/strottos/vim-padre" + +[lib] +path = "src/lib.rs" + +[dependencies] +bytes = "1.0" +futures = "0.3.8" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +tokio = { version = "1.0", features = [ + "io-std", + "io-util", + "process", + "rt" +] } +tokio-util = { version = "0.6", features = ["codec"] } diff --git a/padre/core/src/debugger.rs b/padre/core/src/debugger.rs new file mode 100644 index 0000000..5128052 --- /dev/null +++ b/padre/core/src/debugger.rs @@ -0,0 +1,49 @@ +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FileLocation { + name: String, + line_num: u64, +} + +impl FileLocation { + pub fn new(name: String, line_num: u64) -> Self { + FileLocation { name, line_num } + } + + pub fn name(&self) -> &str { + &self.name[..] + } + + pub fn line_num(&self) -> u64 { + self.line_num + } +} + +/// Variable name +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Variable { + name: String, +} + +impl Variable { + pub fn new(name: String) -> Self { + Variable { name } + } + + pub fn name(&self) -> &str { + &self.name[..] + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DebuggerCmd { + Run, + Breakpoint(FileLocation), + Unbreakpoint(FileLocation), + // Count + StepIn(u64), + // Count + StepOver(u64), + StepOut, + Continue, + Print(Variable), +} diff --git a/padre/core/src/lib.rs b/padre/core/src/lib.rs new file mode 100644 index 0000000..f6a0b89 --- /dev/null +++ b/padre/core/src/lib.rs @@ -0,0 +1,7 @@ +pub mod debugger; +pub mod server; +pub mod util; + +use server::PadreError; + +pub type Result = std::result::Result; diff --git a/padre/core/src/server.rs b/padre/core/src/server.rs new file mode 100644 index 0000000..b7545b5 --- /dev/null +++ b/padre/core/src/server.rs @@ -0,0 +1,90 @@ +/// Shared data structures mostly, largely handled in cli +use std::fmt; +use std::io; + +/// Log level to log at, clients can choose to filter messages at certain log +/// levels +#[derive(Debug)] +pub enum LogLevel { + CRITICAL = 1, + ERROR, + WARN, + INFO, + DEBUG, +} + +/// A notification to be sent to all listeners of an event +/// +/// Takes a String as the command and a vector of JSON values as arguments. For example, a +/// `Notication` with a command `execute` and vector arguments TODO... +#[derive(Clone, Debug, PartialEq)] +pub struct Notification { + cmd: String, + args: Vec, +} + +impl Notification { + /// Create a notification + pub fn new(cmd: String, args: Vec) -> Self { + Notification { cmd, args } + } + + /// Return the notification cmd + pub fn cmd(&self) -> &str { + self.cmd.as_ref() + } + + /// Return the response values + pub fn args(&self) -> &Vec { + &self.args + } +} + +#[derive(Debug)] +pub enum PadreErrorKind { + GenericError, + RequestSyntaxError, + ProcessSpawnError, + DebuggerError, +} + +#[derive(Debug)] +pub struct PadreError { + kind: PadreErrorKind, + error_string: String, + debug_string: String, +} + +impl PadreError { + pub fn new(kind: PadreErrorKind, error_string: String, debug_string: String) -> Self { + PadreError { + kind, + error_string, + debug_string, + } + } + + pub fn get_error_string(&self) -> &str { + &self.error_string + } + + pub fn get_debug_string(&self) -> &str { + &self.debug_string + } +} + +impl fmt::Display for PadreError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "invalid first item to double") + } +} + +impl From for PadreError { + fn from(err: io::Error) -> PadreError { + PadreError::new( + PadreErrorKind::GenericError, + "Generic error".to_string(), + format!("Generic error {}", err), + ) + } +} diff --git a/padre/core/src/util.rs b/padre/core/src/util.rs new file mode 100644 index 0000000..452335f --- /dev/null +++ b/padre/core/src/util.rs @@ -0,0 +1,195 @@ +//! Utilities +//! +//! Various simple utilities for use in PADRE + +use std::env; +use std::io::{self, Write}; +use std::net::TcpListener; +use std::path::{Path, PathBuf}; +use std::process::Stdio; + +use bytes::Bytes; +use futures::prelude::*; +use tokio::io::{stdin, AsyncWriteExt}; +use tokio::process::{Child, ChildStdin, Command}; +use tokio::sync::mpsc::{self, Sender}; +use tokio_util::codec::{BytesCodec, FramedRead}; + +use crate::server::{LogLevel, Notification, PadreError, PadreErrorKind}; +use crate::Result; + +/// Get an unused port on the local system and return it. This port +/// can subsequently be used. +pub fn get_unused_localhost_port() -> u16 { + let listener = TcpListener::bind(format!("127.0.0.1:0")).unwrap(); + listener.local_addr().unwrap().port() +} + +/// Check whether the specified debugger and program to debug exist, including change them to +/// be the full path name if required. If it still can't find both it will panic, otherwise it +/// will start a Child process for running the program. +pub fn check_and_spawn_process( + mut debugger_cmd: Vec, + run_cmd: Vec, +) -> Result { + let mut not_found = None; + + // Try getting the full path if the debugger doesn't exist + if !file_exists(&debugger_cmd[0]) { + debugger_cmd[0] = get_file_full_path(&debugger_cmd[0]); + } + + // Now check the debugger and program to debug exist, if not error + if !file_exists(&run_cmd[0]) { + not_found = Some(&run_cmd[0]); + }; + + if !file_exists(&debugger_cmd[0]) { + not_found = Some(&debugger_cmd[0]); + } + + if let Some(s) = not_found { + return Err(PadreError::new( + PadreErrorKind::ProcessSpawnError, + "Can't spawn, debugger doesn't exist".to_string(), + format!("Can't spawn debugger as {} does not exist", s), + )); + } + + let mut args = vec![]; + + for arg in &debugger_cmd[0..] { + args.push(&arg[..]); + } + + args.push("--"); + + for arg in &run_cmd { + args.push(&arg[..]); + } + + let mut pty_wrapper = env::current_exe().unwrap(); + pty_wrapper.pop(); + pty_wrapper.pop(); + pty_wrapper.pop(); + pty_wrapper.push("ptywrapper.py"); + + Ok(Command::new(pty_wrapper) + .args(&args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to spawn debugger")) +} + +/// Perform setup of listening and forwarding of stdin and return a sender that will forward to the +/// stdin of a process. +pub fn setup_stdin(mut child_stdin: ChildStdin, output_stdin: bool) -> Sender { + let (stdin_tx, mut stdin_rx) = mpsc::channel(1); + let tx = stdin_tx.clone(); + + tokio::spawn(async move { + let tokio_stdin = stdin(); + let mut reader = FramedRead::new(tokio_stdin, BytesCodec::new()); + while let Some(line) = reader.next().await { + let buf = line.unwrap().freeze(); + tx.send(buf).await.unwrap(); + } + }); + + tokio::spawn(async move { + while let Some(text) = stdin_rx.recv().await { + if output_stdin { + io::stdout().write_all(&text).unwrap(); + } + match child_stdin.write(&text).await { + Ok(_) => {} + Err(e) => { + eprintln!("Writing stdin err e: {}", e); + } + }; + } + }); + + stdin_tx +} + +/// Find out the full path of a file based on the PATH environment variable. +pub fn get_file_full_path(cmd: &str) -> String { + let cmd_full_path_buf = env::var_os("PATH") + .and_then(|paths| { + env::split_paths(&paths) + .filter_map(|dir| { + let cmd_full_path = dir.join(&cmd); + if cmd_full_path.is_file() { + Some(cmd_full_path) + } else { + None + } + }) + .next() + }) + .unwrap_or(PathBuf::from(cmd)); + String::from(cmd_full_path_buf.as_path().to_str().unwrap()) +} + +/// Return true if the path specified exists. +pub fn file_exists(path: &str) -> bool { + Path::new(path).exists() +} + +/// Send a message to all listeners +pub fn log_msg(notifier_tx: Sender, level: LogLevel, msg: &str) { + let msg = Notification::new( + "padre#debugger#Log".to_string(), + vec![serde_json::json!(level as u8), serde_json::json!(msg)], + ); + tokio::spawn(async move { + notifier_tx.send(msg).await.unwrap(); + }); +} + +/// Notify about a code position change +pub fn jump_to_position(notifier_tx: Sender, file: &str, line: u64) { + let msg = Notification::new( + "padre#debugger#JumpToPosition".to_string(), + vec![serde_json::json!(file), serde_json::json!(line)], + ); + tokio::spawn(async move { + notifier_tx.send(msg).await.unwrap(); + }); +} + +pub fn serde_json_merge(a: &mut serde_json::Value, b: serde_json::Value) { + if let serde_json::Value::Object(a) = a { + if let serde_json::Value::Object(b) = b { + for (k, v) in b { + if v.is_null() { + a.remove(&k); + } else { + serde_json_merge(a.entry(k).or_insert(serde_json::Value::Null), v); + } + } + + return; + } + } + + *a = b; +} + +#[cfg(test)] +mod tests { + use std::net::TcpListener; + use std::thread; + use std::time::Duration; + + #[test] + fn find_and_use_unused_port() { + let port = super::get_unused_localhost_port(); + thread::sleep(Duration::new(1, 0)); + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap(); + assert_eq!(listener.local_addr().unwrap().port(), port); + } +} diff --git a/padre/debuggers/lldb/Cargo.toml b/padre/debuggers/lldb/Cargo.toml new file mode 100644 index 0000000..8f9de59 --- /dev/null +++ b/padre/debuggers/lldb/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "padre_lldb" +version = "0.2.0" +authors = ["Steven Trotter "] +edition = "2018" +license = "MIT" +repository = "https://github.com/strottos/vim-padre" + +[lib] +path = "src/lib.rs" + +[dependencies] +bytes = "1.0" +futures = "0.3.8" +lazy_static = "1.3.0" +padre_core = { path = "../../core", version = "0.2.0" } +regex = "1.1.2" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +tokio = "1.0" +tokio-util = "0.6" diff --git a/padre/debuggers/lldb/src/debugger.rs b/padre/debuggers/lldb/src/debugger.rs new file mode 100644 index 0000000..99b01bd --- /dev/null +++ b/padre/debuggers/lldb/src/debugger.rs @@ -0,0 +1,257 @@ +//! lldb client debugger +//! +//! The main LLDB Debugger entry point. Handles listening for instructions and +//! communicating through an `LLDBProcess` object. + +use std::process::exit; +use std::time::Instant; + +use super::process::{LLDBProcess, Message}; +use padre_core::debugger::{DebuggerCmd, FileLocation, Variable}; +use padre_core::server::{LogLevel, Notification, PadreError, PadreErrorKind}; +use padre_core::util::log_msg; +use padre_core::Result; + +use tokio::sync::{mpsc, oneshot}; +use tokio::time::timeout_at; + +#[derive(Debug)] +pub struct LLDBDebugger { + queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + process: LLDBProcess, + notifier_tx: mpsc::Sender, +} + +impl LLDBDebugger { + pub fn new( + debugger_cmd: String, + run_cmd: Vec, + queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + notifier_tx: mpsc::Sender, + ) -> LLDBDebugger { + let process = LLDBProcess::new(debugger_cmd, run_cmd, notifier_tx.clone()); + + LLDBDebugger { + queue_rx, + process, + notifier_tx, + } + } + + /// Perform any initial setup including starting LLDB and setting up the stdio analyser stuff + /// - startup lldb and setup the stdio analyser + /// - perform initial setup so we can analyse LLDB properly + #[allow(unreachable_patterns)] + pub async fn start(&mut self) { + self.process.startup().await; + + while let Some((cmd, timeout, tx)) = self.queue_rx.recv().await { + match cmd { + DebuggerCmd::Run => self.run(timeout, tx), + DebuggerCmd::Breakpoint(fl) => self.breakpoint(fl, timeout, tx), + DebuggerCmd::Unbreakpoint(fl) => self.unbreakpoint(fl, timeout, tx), + DebuggerCmd::StepIn(count) => self.step_in(timeout, count, tx), + DebuggerCmd::StepOver(count) => self.step_over(timeout, count, tx), + DebuggerCmd::Continue => self.continue_(timeout, tx), + DebuggerCmd::Print(v) => self.print(v, timeout, tx), + _ => { + tx.send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Bad command".to_string(), + format!("Got a command that isn't supported '{:?}'", cmd), + ))) + .unwrap(); + } + }; + } + + exit(0); + } + + pub async fn stop(&mut self) { + self.process.stop().await; + exit(0); + } + + fn run(&mut self, timeout: Instant, tx_done: oneshot::Sender>) { + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + "Launching process", + ); + + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out spawning process".to_string(), + format!( + "Process spawning timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process.send_msg(Message::ProcessLaunching, Some(tx)); + } + + fn breakpoint( + &mut self, + file_location: FileLocation, + timeout: Instant, + tx_done: oneshot::Sender>, + ) { + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!( + "Setting breakpoint in file {} at line number {}", + file_location.name(), + file_location.line_num() + ), + ); + + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out setting breakpoint".to_string(), + format!( + "Breakpoint setting timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process + .send_msg(Message::Breakpoint(file_location), Some(tx)); + } + + fn unbreakpoint( + &mut self, + file_location: FileLocation, + timeout: Instant, + tx_done: oneshot::Sender>, + ) { + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out removing breakpoint".to_string(), + format!( + "Breakpoint removing timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process + .send_msg(Message::Unbreakpoint(file_location), Some(tx)); + } + + fn step_in( + &mut self, + _timeout: Instant, + count: u64, + tx_done: oneshot::Sender>, + ) { + self.process.send_msg(Message::StepIn(count), None); + + tx_done.send(Ok(serde_json::json!({}))).unwrap(); + } + + fn step_over( + &mut self, + _timeout: Instant, + count: u64, + tx_done: oneshot::Sender>, + ) { + self.process.send_msg(Message::StepOver(count), None); + + tx_done.send(Ok(serde_json::json!({}))).unwrap(); + } + + fn continue_( + &mut self, + _timeout: Instant, + tx_done: oneshot::Sender>, + ) { + self.process.send_msg(Message::Continue, None); + + tx_done.send(Ok(serde_json::json!({}))).unwrap(); + } + + fn print( + &mut self, + variable: Variable, + timeout: Instant, + tx_done: oneshot::Sender>, + ) { + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out printing variable".to_string(), + format!( + "Printing variable timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process + .send_msg(Message::PrintVariable(variable.clone()), Some(tx)); + } +} diff --git a/padre/debuggers/lldb/src/lib.rs b/padre/debuggers/lldb/src/lib.rs new file mode 100644 index 0000000..db2e278 --- /dev/null +++ b/padre/debuggers/lldb/src/lib.rs @@ -0,0 +1,28 @@ +//! The LLDB debugger module + +#[macro_use] +extern crate lazy_static; + +use std::time::Instant; + +use tokio::sync::{mpsc, oneshot}; + +use padre_core::debugger::DebuggerCmd; +use padre_core::server::Notification; +use padre_core::Result; + +mod debugger; +mod process; + +pub fn get_debugger( + debugger_cmd: String, + run_cmd: Vec, + queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + notifier_tx: mpsc::Sender, +) -> debugger::LLDBDebugger { + debugger::LLDBDebugger::new(debugger_cmd, run_cmd, queue_rx, notifier_tx) +} diff --git a/padre/debuggers/lldb/src/process.rs b/padre/debuggers/lldb/src/process.rs new file mode 100644 index 0000000..e5635ad --- /dev/null +++ b/padre/debuggers/lldb/src/process.rs @@ -0,0 +1,756 @@ +//! lldb process handler +//! +//! This module performs the basic setup of and interfacing with LLDB. It will +//! analyse the output of the text and work out what is happening then. + +use std::io::{self, Write}; +use std::sync::{Arc, Mutex}; + +use padre_core::debugger::{FileLocation, Variable}; +use padre_core::server::{LogLevel, Notification, PadreError, PadreErrorKind}; +use padre_core::util::{check_and_spawn_process, jump_to_position, log_msg}; +use padre_core::Result; + +use bytes::{BufMut, Bytes, BytesMut}; +use futures::prelude::*; +use regex::Regex; +use tokio::io::{self as tokio_io, AsyncWriteExt}; +use tokio::process::{Child, ChildStdin, ChildStdout}; +use tokio::sync::{mpsc, oneshot}; +use tokio_util::codec::{BytesCodec, FramedRead}; + +/// Messages that can be sent to LLDB for processing +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Message { + LLDBSetup, + ProcessLaunching, + Breakpoint(FileLocation), + Unbreakpoint(FileLocation), + // count + StepIn(u64), + // count + StepOver(u64), + Continue, + PrintVariable(Variable), + Custom, +} + +/// Current status of LLDB +/// +/// We start in Processing(LLDBSetup), then when LLDB has started up properly it moves to +/// `Listening`. Whenever a message is send to LLDB it changes to `Processing(message)` where +/// `message` is anything of type Message above. A Custom message indicates that it has been typed +/// in by the user and PADRE is unaware of what was typed (though will still pick up consequences +/// like line changing, etc). +#[derive(Clone, Debug, PartialEq)] +pub enum LLDBStatus { + Listening, + Processing((Message, Option)), +} + +/// Main handler for spawning the LLDB process +/// +/// The status contains the process id of any PID running (or None if there isn't one) and the +#[derive(Debug)] +pub struct LLDBProcess { + lldb_process: Child, + lldb_stdin_tx: mpsc::Sender, + analyser: Arc>, + notifier_tx: mpsc::Sender, +} + +impl LLDBProcess { + /// Create and setup LLDB + /// + /// Includes spawning the LLDB process and all the relevant stdio handlers. In particular: + /// - Sets up a `ReadOutput` from `util.rs` in order to read output from LLDB; + /// - Sets up a thread to read stdin and forward it onto LLDB stdin; + /// - Checks that LLDB and the program to be ran both exist, otherwise panics. + pub fn new( + debugger_cmd: String, + run_cmd: Vec, + notifier_tx: mpsc::Sender, + ) -> Self { + let mut lldb_process = match check_and_spawn_process(vec![debugger_cmd], run_cmd) { + Ok(process) => process, + Err(err) => panic!("Can't spawn LLDB: {:?}", err), + }; + + let analyser = Arc::new(Mutex::new(LLDBAnalyser::new(notifier_tx.clone()))); + + // NB: Don't need stderr as it's taken from a process spawned with ptywrapper.py that will + // wrap stderr into stdout. + LLDBProcess::setup_stdout( + lldb_process + .stdout + .take() + .expect("LLDB process did not have a handle to stdout"), + analyser.clone(), + ); + let lldb_stdin_tx = LLDBProcess::setup_stdin( + lldb_process + .stdin + .take() + .expect("Python process did not have a handle to stdin"), + analyser.clone(), + ); + + LLDBProcess { + lldb_process, + lldb_stdin_tx, + analyser, + notifier_tx, + } + } + + pub async fn startup(&mut self) { + match self.get_status() { + LLDBStatus::Listening => {} + _ => { + let (tx, rx) = oneshot::channel(); + self.analyser.lock().unwrap().send_output_tx = Some(tx); + rx.await.unwrap().unwrap(); + } + }; + + for msg in vec!( + Bytes::from("settings set stop-line-count-after 0"), + Bytes::from("settings set stop-line-count-before 0"), + Bytes::from("settings set frame-format frame #${frame.index}{ at ${line.file.fullpath}:${line.number}}\\n"), + Bytes::from("breakpoint set --name main"), + ) { + let (tx, rx) = oneshot::channel(); + self.analyser + .lock() + .unwrap() + .get_output(Message::LLDBSetup, msg.clone(), tx); + + self.lldb_stdin_tx.send(msg).await.unwrap(); + + rx.await.unwrap().unwrap(); + } + } + + pub async fn stop(&mut self) { + self.lldb_process.kill().await.unwrap(); + } + + pub fn get_status(&self) -> LLDBStatus { + self.analyser.lock().unwrap().lldb_status.clone() + } + + /// Perform setup of listening and forwarding of stdin and return a sender that will forward to the + /// stdin of a process. + fn setup_stdin( + mut child_stdin: ChildStdin, + analyser: Arc>, + ) -> mpsc::Sender { + let (stdin_tx, mut stdin_rx) = mpsc::channel(32); + let tx = stdin_tx.clone(); + + tokio::spawn(async move { + let tokio_stdin = tokio_io::stdin(); + let mut reader = FramedRead::new(tokio_stdin, BytesCodec::new()); + while let Some(line) = reader.next().await { + let buf = line.unwrap().freeze(); + analyser + .lock() + .unwrap() + .set_status(LLDBStatus::Processing((Message::Custom, Some(buf.clone())))); + tx.send(buf).await.unwrap(); + } + }); + + tokio::spawn(async move { + while let Some(text) = stdin_rx.recv().await { + match child_stdin.write(&text).await { + Ok(_) => {} + Err(e) => { + eprintln!("Writing stdin err e: {}", e); + } + }; + // Append \n if necessary + let len = text.len(); + if &text[len - 1..len] != "\n".as_bytes() { + match child_stdin.write(&[10 as u8]).await { + Ok(_) => {} + Err(e) => { + eprintln!("Writing stdin err e: {}", e); + } + } + } + } + }); + + stdin_tx + } + + /// Perform setup of reading LLDB stdout, analysing it and writing it back to stdout. + fn setup_stdout(stdout: ChildStdout, analyser: Arc>) { + tokio::spawn(async move { + let mut reader = FramedRead::new(stdout, BytesCodec::new()); + while let Some(Ok(text)) = reader.next().await { + analyser.lock().unwrap().handle_output(text); + } + }); + } + + /// Send a message to write to stdin + pub fn send_msg( + &mut self, + message: Message, + tx_done: Option>>, + ) { + let lldb_stdin_tx = self.lldb_stdin_tx.clone(); + let analyser = self.analyser.clone(); + + let (_, process_pid) = analyser.lock().unwrap().get_details(); + + let msg = match &message { + Message::ProcessLaunching => { + match process_pid { + Some(pid) => { + match tx_done { + Some(tx) => { + let res = Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Process already running".to_string(), + format!("Process with pid '{}' already running", pid), + )); + tx.send(res).unwrap(); + } + None => {} + } + return; + } + None => {} + } + vec![Bytes::from("process launch")] + } + Message::Breakpoint(fl) => vec![Bytes::from(format!( + "breakpoint set --file {} --line {}", + fl.name(), + fl.line_num() + ))], + Message::Unbreakpoint(fl) => return self.remove_breakpoint(fl.clone(), tx_done), + Message::StepIn(count) => return self.step(Message::StepIn(*count), tx_done), + Message::StepOver(count) => return self.step(Message::StepOver(*count), tx_done), + Message::Continue => vec![Bytes::from("thread continue")], + Message::PrintVariable(v) => return self.print_variable(v.clone(), tx_done), + _ => unreachable!(), + }; + + tokio::spawn(async move { + let res = Ok(serde_json::json!({})); + + for b in msg { + let (tx, rx) = oneshot::channel(); + + analyser + .lock() + .unwrap() + .get_output(message.clone(), b.clone(), tx); + + lldb_stdin_tx.send(b).map(move |_| {}).await; + + rx.await.unwrap().unwrap(); + } + match tx_done { + Some(tx) => { + tx.send(res).unwrap(); + } + _ => {} + } + }); + } + + fn step(&self, message: Message, tx_done: Option>>) { + let lldb_stdin_tx = self.lldb_stdin_tx.clone(); + let analyser = self.analyser.clone(); + + let (bytes, count) = match message { + Message::StepIn(count) => (Bytes::from("thread step-in"), count), + Message::StepOver(count) => (Bytes::from("thread step-over"), count), + _ => unreachable!(), + }; + + tokio::spawn(async move { + analyser.lock().unwrap().reporting_location = false; + + for i in 0..count { + let (tx, rx) = oneshot::channel(); + + if i == count - 1 { + analyser.lock().unwrap().reporting_location = true; + } + + analyser + .lock() + .unwrap() + .get_output(message.clone(), bytes.clone(), tx); + + lldb_stdin_tx.send(bytes.clone()).map(move |_| {}).await; + + rx.await.unwrap().unwrap(); + } + + match tx_done { + Some(tx) => { + tx.send(Ok(serde_json::json!({}))).unwrap(); + } + _ => {} + } + }); + } + + fn print_variable( + &self, + v: Variable, + tx_done: Option>>, + ) { + let lldb_stdin_tx = self.lldb_stdin_tx.clone(); + let analyser = self.analyser.clone(); + + tokio::spawn(async move { + let (tx, rx) = oneshot::channel(); + + let msg = Bytes::from(format!("frame variable {}", v.name())); + + analyser + .lock() + .unwrap() + .get_output(Message::PrintVariable(v.clone()), msg.clone(), tx); + + lldb_stdin_tx.send(msg).map(move |_| {}).await; + + tx_done + .unwrap() + .send(get_variable_info(rx.await.unwrap(), &v)) + .unwrap(); + }); + } + + fn remove_breakpoint( + &self, + fl: FileLocation, + tx_done: Option>>, + ) { + let lldb_stdin_tx = self.lldb_stdin_tx.clone(); + let analyser = self.analyser.clone(); + + let notifier_tx = self.notifier_tx.clone(); + + tokio::spawn(async move { + let msg = Message::Unbreakpoint(fl.clone()); + + let (tx, rx) = oneshot::channel(); + + let bytes = Bytes::from("breakpoint list"); + + analyser + .lock() + .unwrap() + .get_output(msg.clone(), bytes.clone(), tx); + + lldb_stdin_tx.send(bytes).map(move |_| {}).await; + + // TODO: Errors + let values = get_breakpoints_set_at(rx.await.unwrap(), &fl); + + for bkt_num in values { + let (tx, rx) = oneshot::channel(); + + let bytes = Bytes::from(format!("breakpoint delete {}", bkt_num)); + + analyser + .lock() + .unwrap() + .get_output(msg.clone(), bytes.clone(), tx); + + lldb_stdin_tx.send(bytes).map(move |_| {}).await; + + rx.await.unwrap().unwrap(); + } + + log_msg( + notifier_tx, + LogLevel::INFO, + &format!( + "Removed breakpoint in file {} and line number {}", + fl.name(), + fl.line_num() + ), + ); + + match tx_done { + Some(tx) => { + tx.send(Ok(serde_json::json!({}))).unwrap(); + } + _ => {} + } + }); + } +} + +fn get_variable_info(output: Result, variable: &Variable) -> Result { + let details = output.unwrap(); + + if details == "error: invalid process" + || details + == format!( + "error: no variable named '{}' found in this frame", + variable.name(), + ) + { + return Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Variable not found".to_string(), + format!("Variable '{}' not found", variable.name()), + )); + } + + let mut right_bracket_index = 2; + if &details[0..1] != "(" { + panic!("Can't understand printing variable output: {:?}", details,); + } + + while &details[right_bracket_index - 1..right_bracket_index] != ")" { + right_bracket_index += 1; + } + + let type_ = details[1..right_bracket_index - 1].to_string(); + + let mut equals_index = 2; + + while &details[equals_index - 1..equals_index] != "=" { + equals_index += 1; + } + + let name = details[right_bracket_index + 1..equals_index - 2].to_string(); + let value = details[equals_index + 1..].to_string(); + + Ok(serde_json::json!({ + "variable": name, + "type": type_, + "value": value, + })) +} + +fn get_breakpoints_set_at(output: Result, fl: &FileLocation) -> Vec { + // TODO: Valid? Catches all? + lazy_static! { + static ref RE_BREAKPOINT: Regex = + Regex::new("(\\d+): file = '(.*)', line = (\\d+), exact_match = 0, locations = 1$") + .unwrap(); + } + + let details = output.unwrap(); + + let mut ret = vec![]; + + for line in details.split("\r\n") { + for cap in RE_BREAKPOINT.captures_iter(line) { + let breakpoint_num = cap[1].parse::().unwrap(); + let file = cap[2].to_string(); + let line = cap[3].parse::().unwrap(); + + if fl == &FileLocation::new(file, line) { + ret.push(breakpoint_num); + } + } + } + + ret +} + +/// Perform analysis on the output from the LLDB process and store data for when we need to access +/// it. +#[derive(Debug)] +pub struct LLDBAnalyser { + lldb_status: LLDBStatus, + process_pid: Option, + send_output_tx: Option>>, + notifier_tx: mpsc::Sender, + output: BytesMut, + reporting_location: bool, +} + +impl LLDBAnalyser { + pub fn new(notifier_tx: mpsc::Sender) -> Self { + LLDBAnalyser { + lldb_status: LLDBStatus::Processing((Message::LLDBSetup, None)), + process_pid: None, + send_output_tx: None, + notifier_tx, + output: BytesMut::new(), + reporting_location: true, + } + } + + pub fn handle_output(&mut self, bytes: BytesMut) { + let text = String::from_utf8_lossy(&bytes[..]).to_string(); + print!("{}", text); + io::stdout().flush().unwrap(); + + // Check if we're printing a variable as we record the data for later and only print when + // we're done collecting output + match &self.lldb_status { + LLDBStatus::Processing((_, m)) => { + match m { + Some(msg) => { + let mut from = 0; + let to = text.len(); + + if self.output.len() == 0 + // 2 extra for \r\n + && to >= msg.len() + 2 + && &text[0..msg.len()] == msg + { + from += msg.len() + 2; + } + + self.output.put(&bytes[from..to]); + } + None => {} + }; + } + _ => {} + } + + // Then check everything else + for line in text.split("\r\n") { + match &self.lldb_status { + LLDBStatus::Processing((msg, _)) => match msg { + Message::Breakpoint(_) => { + self.check_breakpoint(line); + } + Message::ProcessLaunching => { + self.check_process_launched(line); + self.check_location(line); + self.check_process_exited(line); + } + Message::StepIn(_) | Message::StepOver(_) | Message::Continue => { + self.check_location(line); + self.check_process_exited(line); + self.check_process_and_thread_running(line); + } + Message::Custom => { + self.check_process_launched(line); + self.check_breakpoint(line); + self.check_location(line); + self.check_process_exited(line); + } + _ => {} + }, + // Seems to be some bug in LLDB where sometimes it will still output this stuff + // after it went back into listening mode. + LLDBStatus::Listening => { + self.check_location(line); + self.check_process_exited(line); + } + }; + } + + match text.split("\r\n").last() { + Some(l) => { + if l == "(lldb) " { + match &self.lldb_status { + LLDBStatus::Processing(_) => { + let tx_option = self.send_output_tx.take(); + match tx_option { + Some(tx) => { + let mut to = self.output.len(); + let lldb_prompt_length = "\r\n(lldb) ".len(); + if to >= lldb_prompt_length + && &self.output[to - lldb_prompt_length..to] + == "\r\n(lldb) ".as_bytes() + { + to -= lldb_prompt_length; + } + + let output = + String::from_utf8_lossy(&self.output[0..to]).to_string(); + + tx.send(Ok(output)).unwrap(); + } + None => {} + } + } + _ => {} + } + self.output = BytesMut::new(); + self.set_status(LLDBStatus::Listening); + } + } + None => {} + } + } + + pub fn get_details(&self) -> (LLDBStatus, Option) { + (self.lldb_status.clone(), self.process_pid.clone()) + } + + pub fn set_status(&mut self, lldb_status: LLDBStatus) { + match &self.lldb_status { + LLDBStatus::Listening => {} + _ => { + match lldb_status { + LLDBStatus::Listening => { + let tx_option = self.send_output_tx.take(); + match tx_option { + Some(tx) => { + tx.send(Ok("".to_string())).unwrap(); + } + _ => {} + }; + } + _ => {} + }; + } + } + self.lldb_status = lldb_status; + } + + /// Sets up the analyser ready for analysing the message. + /// + /// It sets the status of the analyser to Processing for that message and if given + /// it marks the analyser to send a message to `tx_done` to indicate when the + /// message is processed. + pub fn get_output( + &mut self, + msg: Message, + cmd: Bytes, + tx_done: oneshot::Sender>, + ) { + self.lldb_status = LLDBStatus::Processing((msg, Some(cmd))); + self.send_output_tx = Some(tx_done); + } + + fn check_breakpoint(&mut self, line: &str) { + lazy_static! { + static ref RE_BREAKPOINT: Regex = Regex::new( + "Breakpoint (\\d+): where = .* at (.*):(\\d+):\\d+, address = 0x[0-9a-f]*$" + ) + .unwrap(); + static ref RE_BREAKPOINT_2: Regex = + Regex::new("Breakpoint (\\d+): where = .* at (.*):(\\d+), address = 0x[0-9a-f]*$") + .unwrap(); + static ref RE_BREAKPOINT_PENDING: Regex = + Regex::new("Breakpoint (\\d+): no locations \\(pending\\)\\.$").unwrap(); + } + + for cap in RE_BREAKPOINT.captures_iter(line) { + let file = cap[2].to_string(); + let line = cap[3].parse::().unwrap(); + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Breakpoint set file={}, line={}", file, line), + ); + return; + } + + for cap in RE_BREAKPOINT_2.captures_iter(line) { + let file = cap[2].to_string(); + let line = cap[3].parse::().unwrap(); + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Breakpoint set file={}, line={}", file, line), + ); + return; + } + + for _ in RE_BREAKPOINT_PENDING.captures_iter(line) { + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Breakpoint pending"), + ); + } + } + + fn check_location(&mut self, line: &str) { + if !self.reporting_location { + return; + } + + lazy_static! { + static ref RE_STOPPED_AT_POSITION: Regex = Regex::new(" *frame #\\d.*$").unwrap(); + static ref RE_JUMP_TO_POSITION: Regex = + Regex::new("^ *frame #\\d at (\\S+):(\\d+)$").unwrap(); + } + + for _ in RE_STOPPED_AT_POSITION.captures_iter(line) { + let mut found = false; + for cap in RE_JUMP_TO_POSITION.captures_iter(line) { + found = true; + let file = cap[1].to_string(); + let line = cap[2].parse::().unwrap(); + jump_to_position(self.notifier_tx.clone(), &file, line); + } + + if !found { + log_msg( + self.notifier_tx.clone(), + LogLevel::WARN, + "Stopped at unknown position", + ); + } + } + } + + fn check_process_launched(&mut self, line: &str) { + lazy_static! { + static ref RE_PROCESS_LAUNCHED: Regex = + Regex::new(r#"Process (\d+) launched: '.*' \(.*\)$"#).unwrap(); + } + + for cap in RE_PROCESS_LAUNCHED.captures_iter(line) { + let process_pid = cap[1].to_string(); + + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Process {} launched", process_pid), + ); + + self.process_pid = Some(process_pid); + } + } + + fn check_process_exited(&mut self, line: &str) { + lazy_static! { + static ref RE_PROCESS_EXITED: Regex = + Regex::new("Process (\\d+) exited with status = (\\d+) \\(0x[0-9a-f]*\\) *$") + .unwrap(); + } + + for cap in RE_PROCESS_EXITED.captures_iter(line) { + let pid = cap[1].parse::().unwrap(); + let exit_code = cap[2].parse::().unwrap(); + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Process {} exited with exit code {}", pid, exit_code), + ); + + self.process_pid = None; + } + } + + fn check_process_and_thread_running(&mut self, line: &str) { + lazy_static! { + static ref RE_NOT_RUNNING: Regex = + Regex::new(r#"^error: invalid (process|thread)$"#).unwrap(); + } + + for _ in RE_NOT_RUNNING.captures_iter(line) { + log_msg( + self.notifier_tx.clone(), + LogLevel::WARN, + "No process running", + ); + } + } +} diff --git a/padre/debuggers/python/Cargo.toml b/padre/debuggers/python/Cargo.toml new file mode 100644 index 0000000..b9c7d54 --- /dev/null +++ b/padre/debuggers/python/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "padre_python" +version = "0.2.0" +authors = ["Steven Trotter "] +edition = "2018" +license = "MIT" +repository = "https://github.com/strottos/vim-padre" + +[lib] +path = "src/lib.rs" + +[dependencies] +bytes = "1.0" +futures = "0.3.8" +lazy_static = "1.3.0" +padre_core = { path = "../../core", version = "0.2.0" } +regex = "1.1.2" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +tokio = { version = "1.0", features = [ "process" ] } +tokio-util = "0.6" diff --git a/padre/debuggers/python/src/debugger.rs b/padre/debuggers/python/src/debugger.rs new file mode 100644 index 0000000..b39454a --- /dev/null +++ b/padre/debuggers/python/src/debugger.rs @@ -0,0 +1,255 @@ +//! Python debugger +//! +//! The main PDB Debugger entry point. Handles listening for instructions and +//! communicating through an `PythonProcess` object. + +use std::process::exit; +use std::time::Instant; + +use super::process::{Message, PythonProcess}; +use padre_core::debugger::{DebuggerCmd, FileLocation, Variable}; +use padre_core::server::{LogLevel, Notification, PadreError, PadreErrorKind}; +use padre_core::util::log_msg; +use padre_core::Result; + +use tokio::sync::{mpsc, oneshot}; +use tokio::time::timeout_at; + +#[derive(Debug)] +pub struct PythonDebugger { + queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + process: PythonProcess, + notifier_tx: mpsc::Sender, +} + +impl PythonDebugger { + pub fn new( + debugger_cmd: String, + run_cmd: Vec, + queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + notifier_tx: mpsc::Sender, + ) -> PythonDebugger { + let process = PythonProcess::new(debugger_cmd, run_cmd, notifier_tx.clone()); + + PythonDebugger { + queue_rx, + process, + notifier_tx, + } + } + + /// Perform any initial setup including starting PDB and setting up the stdio analyser stuff + /// - startup pdb and setup the stdio analyser + /// - perform initial setup so we can analyse PDB properly + #[allow(unreachable_patterns)] + pub async fn start(&mut self) { + while let Some((cmd, timeout, tx)) = self.queue_rx.recv().await { + match cmd { + DebuggerCmd::Run => self.run(timeout, tx), + DebuggerCmd::Breakpoint(fl) => self.breakpoint(fl, timeout, tx), + DebuggerCmd::Unbreakpoint(fl) => self.unbreakpoint(fl, timeout, tx), + DebuggerCmd::StepIn(count) => self.step_in(timeout, count, tx), + DebuggerCmd::StepOver(count) => self.step_over(timeout, count, tx), + DebuggerCmd::Continue => self.continue_(timeout, tx), + DebuggerCmd::Print(v) => self.print(v, timeout, tx), + _ => { + tx.send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Bad command".to_string(), + format!("Got a command that isn't supported '{:?}'", cmd), + ))) + .unwrap(); + } + }; + } + + exit(0); + } + + pub async fn stop(&mut self) { + self.process.stop().await; + exit(0); + } + + fn run(&mut self, timeout: Instant, tx_done: oneshot::Sender>) { + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + "Restarting process", + ); + + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out spawning process".to_string(), + format!( + "Process spawning timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process.send_msg(Message::Restart, Some(tx)); + } + + fn breakpoint( + &mut self, + file_location: FileLocation, + timeout: Instant, + tx_done: oneshot::Sender>, + ) { + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!( + "Setting breakpoint in file {} at line number {}", + file_location.name(), + file_location.line_num() + ), + ); + + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out setting breakpoint".to_string(), + format!( + "Breakpoint setting timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process + .send_msg(Message::Breakpoint(file_location), Some(tx)); + } + + fn unbreakpoint( + &mut self, + file_location: FileLocation, + timeout: Instant, + tx_done: oneshot::Sender>, + ) { + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out removing breakpoint".to_string(), + format!( + "Breakpoint removing timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process + .send_msg(Message::Unbreakpoint(file_location), Some(tx)); + } + + fn step_in( + &mut self, + _timeout: Instant, + count: u64, + tx_done: oneshot::Sender>, + ) { + self.process.send_msg(Message::StepIn(count), None); + + tx_done.send(Ok(serde_json::json!({}))).unwrap(); + } + + fn step_over( + &mut self, + _timeout: Instant, + count: u64, + tx_done: oneshot::Sender>, + ) { + self.process.send_msg(Message::StepOver(count), None); + + tx_done.send(Ok(serde_json::json!({}))).unwrap(); + } + + fn continue_( + &mut self, + _timeout: Instant, + tx_done: oneshot::Sender>, + ) { + self.process.send_msg(Message::Continue, None); + + tx_done.send(Ok(serde_json::json!({}))).unwrap(); + } + + fn print( + &mut self, + variable: Variable, + timeout: Instant, + tx_done: oneshot::Sender>, + ) { + let (tx, rx) = oneshot::channel(); + + tokio::spawn(async move { + let started = Instant::now(); + match timeout_at(tokio::time::Instant::from_std(timeout), rx).await { + Ok(ret) => { + tx_done.send(ret.unwrap()).unwrap(); + } + Err(_) => { + tx_done + .send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Timed out printing variable".to_string(), + format!( + "Printing variable timed out after {:?}", + timeout.duration_since(started) + ), + ))) + .unwrap(); + } + } + }); + + self.process + .send_msg(Message::PrintVariable(variable.clone()), Some(tx)); + } +} diff --git a/padre/debuggers/python/src/lib.rs b/padre/debuggers/python/src/lib.rs new file mode 100644 index 0000000..1b3374a --- /dev/null +++ b/padre/debuggers/python/src/lib.rs @@ -0,0 +1,28 @@ +//! The Python debugger module + +#[macro_use] +extern crate lazy_static; + +use std::time::Instant; + +use tokio::sync::{mpsc, oneshot}; + +use padre_core::debugger::DebuggerCmd; +use padre_core::server::Notification; +use padre_core::Result; + +mod debugger; +mod process; + +pub fn get_debugger( + debugger_cmd: String, + run_cmd: Vec, + queue_rx: mpsc::Receiver<( + DebuggerCmd, + Instant, + oneshot::Sender>, + )>, + notifier_tx: mpsc::Sender, +) -> debugger::PythonDebugger { + debugger::PythonDebugger::new(debugger_cmd, run_cmd, queue_rx, notifier_tx) +} diff --git a/padre/debuggers/python/src/process.rs b/padre/debuggers/python/src/process.rs new file mode 100644 index 0000000..9d9b981 --- /dev/null +++ b/padre/debuggers/python/src/process.rs @@ -0,0 +1,766 @@ +//! Python process handler +//! +//! This module performs the basic setup of and interfacing with PDB. It will +//! analyse the output of the text and work out what is happening then. + +use std::env; +use std::io::{self, Write}; +use std::path::Path; +use std::process::Stdio; +use std::sync::{Arc, Mutex}; + +use padre_core::debugger::{FileLocation, Variable}; +use padre_core::server::{LogLevel, Notification, PadreError, PadreErrorKind}; +#[cfg(not(test))] +use padre_core::util::{file_exists, get_file_full_path}; +use padre_core::util::{jump_to_position, log_msg}; +use padre_core::Result; + +use bytes::{Bytes, BytesMut}; +use futures::prelude::*; +use regex::Regex; +use tokio::io::{self as tokio_io, AsyncWriteExt}; +use tokio::process::{Child, ChildStdin, ChildStdout, Command}; +use tokio::sync::{mpsc, oneshot}; +use tokio_util::codec::{BytesCodec, FramedRead}; + +/// Messages that can be sent to PDB for processing +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Message { + Launching, + Restart, + Breakpoint(FileLocation), + Unbreakpoint(FileLocation), + // count + StepIn(u64), + // count + StepOver(u64), + Continue, + PrintVariable(Variable), + Custom, +} + +/// Current status of PDB +#[derive(Debug, Clone, PartialEq)] +pub enum PDBStatus { + Listening, + Processing(Message), +} + +/// Work out the arguments to send to python based on the python command given and the +/// run command specified +fn get_python_args<'a>(debugger_cmd: &str, run_cmd: Vec<&'a str>) -> Result> { + let mut python_args = vec![]; + let mut script_args = vec![]; + + // Now check the debugger and program to debug exist, if not error + #[cfg(not(test))] + { + // Try getting the full path if the debugger doesn't exist + if !file_exists(&debugger_cmd) { + let debugger_cmd = get_file_full_path(&debugger_cmd); + + if !file_exists(&debugger_cmd) { + return Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Can't spawn debugger".to_string(), + format!("Can't spawn debugger as {} does not exist", debugger_cmd), + )); + } + } + } + + python_args.push("-m"); + python_args.push("pdb"); + + // If we have the command `python -m mymod` say and `python` is specified + // as the debugger then we have then we don't want to run + // `python -m pdb -- -m mymod` + // On the other hand if we specified `./script.py -a test` we want that to + // run + // `python -m pdb -- ./script.py -a test` + // so we keep track of whether they're likely to be a python arg or a script + // arg here. + // + // tl;dr We assume all args are script args if the 0th element doesn't + // match the debugger, if it does we wait until we see `--` and then we + // assume script args. + let mut is_script_arg = true; + + for (i, arg) in run_cmd.iter().enumerate() { + // Skip the python part if specified as we add that from the -d option + if i == 0 { + let debugger_cmd_path = Path::new(debugger_cmd); + + let debugger_cmd = match debugger_cmd_path.file_name() { + Some(s) => s.to_str().unwrap(), + None => debugger_cmd, + }; + + if debugger_cmd == *arg { + is_script_arg = false; + continue; + } else { + is_script_arg = true; + } + } + + if *arg == "--" { + is_script_arg = true; + continue; + } + + if is_script_arg { + script_args.push(&arg[..]); + } else { + python_args.push(arg); + } + } + + if script_args.len() > 0 { + python_args.push("--"); + python_args.append(&mut script_args); + } + + Ok(python_args) +} + +/// Main handler for spawning the PDB process +/// +/// The status contains the process id of any PID running (or None if there isn't one) and the +#[derive(Debug)] +pub struct PythonProcess { + process: Child, + stdin_tx: mpsc::Sender, + analyser: Arc>, + notifier_tx: mpsc::Sender, +} + +impl PythonProcess { + /// Create and setup PDB + /// + /// Includes spawning the PDB process and all the relevant stdio handlers. In particular: + /// - Sets up a `ReadOutput` from `util.rs` in order to read output from PDB; + /// - Sets up a thread to read stdin and forward it onto PDB stdin; + /// - Checks that PDB and the program to be ran both exist, otherwise panics. + pub fn new( + debugger_cmd: String, + run_cmd: Vec, + notifier_tx: mpsc::Sender, + ) -> Self { + let args = + match get_python_args(&debugger_cmd[..], run_cmd.iter().map(|x| &x[..]).collect()) { + Ok(args) => args, + Err(err) => panic!("Can't spawn PDB, unknown args: {:?}", err), + }; + + let mut pty_wrapper = env::current_exe().unwrap(); + pty_wrapper.pop(); + pty_wrapper.pop(); + pty_wrapper.pop(); + pty_wrapper.push("ptywrapper.py"); + + let mut process = Command::new(pty_wrapper) + .arg(&debugger_cmd) + .args(&args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to spawn debugger"); + + let analyser = Arc::new(Mutex::new(PDBAnalyser::new( + notifier_tx.clone(), + format!("{}", process.id().unwrap()), + ))); + + // NB: Don't need stderr as it's taken from a process spawned with ptywrapper.py that will + // wrap stderr into stdout. + PythonProcess::setup_stdout( + process + .stdout + .take() + .expect("PDB process did not have a handle to stdout"), + analyser.clone(), + ); + let stdin_tx = PythonProcess::setup_stdin( + process + .stdin + .take() + .expect("Python process did not have a handle to stdin"), + analyser.clone(), + ); + + PythonProcess { + process, + stdin_tx, + analyser, + notifier_tx, + } + } + + pub async fn stop(&mut self) { + self.process.kill().await.unwrap(); + } + + /// Send a message to write to stdin + pub fn send_msg( + &mut self, + message: Message, + tx_done: Option>>, + ) { + let stdin_tx = self.stdin_tx.clone(); + let analyser = self.analyser.clone(); + + let send_msg = match &message { + Message::Launching => unreachable!(), + Message::Restart => Bytes::from("run\n"), + Message::Breakpoint(fl) => { + Bytes::from(format!("break {}:{}\n", fl.name(), fl.line_num())) + } + Message::Unbreakpoint(fl) => { + Bytes::from(format!("clear {}:{}\n", fl.name(), fl.line_num())) + } + Message::StepIn(count) => return self.step(Message::StepIn(*count), tx_done), + Message::StepOver(count) => return self.step(Message::StepOver(*count), tx_done), + Message::Continue => Bytes::from("continue\n"), + Message::PrintVariable(v) => { + return self.send_print_variable_message(v.clone(), tx_done) + } + Message::Custom => todo!(), + }; + + tokio::spawn(async move { + analyser + .lock() + .unwrap() + .analyse_message(message.clone(), tx_done); + + stdin_tx.send(send_msg).map(move |_| {}).await; + }); + } + + fn step(&self, message: Message, tx_done: Option>>) { + let stdin_tx = self.stdin_tx.clone(); + let analyser = self.analyser.clone(); + + let (bytes, count) = match message { + Message::StepIn(count) => (Bytes::from("step\n"), count), + Message::StepOver(count) => (Bytes::from("next\n"), count), + _ => unreachable!(), + }; + + tokio::spawn(async move { + analyser.lock().unwrap().reporting_location = false; + + for i in 0..count { + let (tx, rx) = oneshot::channel(); + + if i == count - 1 { + analyser.lock().unwrap().reporting_location = true; + } + + analyser + .lock() + .unwrap() + .analyse_message(message.clone(), Some(tx)); + + stdin_tx.send(bytes.clone()).map(move |_| {}).await; + + rx.await.unwrap().unwrap(); + } + + match tx_done { + Some(tx) => { + tx.send(Ok(serde_json::json!({}))).unwrap(); + } + _ => {} + } + }); + } + + fn send_print_variable_message( + &mut self, + v: Variable, + tx_done: Option>>, + ) { + let analyser = self.analyser.clone(); + let stdin_tx = self.stdin_tx.clone(); + + let variable_name = v.name().to_string(); + + tokio::spawn(async move { + let (tx, rx) = oneshot::channel(); + + analyser.lock().unwrap().analyse_message( + Message::PrintVariable(Variable::new(variable_name.clone())), + Some(tx), + ); + + stdin_tx + .send(Bytes::from(format!("print({})\n", v.name()))) + .map(move |_| {}) + .await; + + // TODO: Errors + let value = rx.await.unwrap(); + + let (tx, rx) = oneshot::channel(); + + analyser.lock().unwrap().analyse_message( + Message::PrintVariable(Variable::new(format!("type({})", variable_name))), + Some(tx), + ); + + stdin_tx + .send(Bytes::from(format!("print(type({}))\n", v.name()))) + .map(move |_| {}) + .await; + + // TODO: Errors + let type_ = rx.await.unwrap(); + + tx_done + .unwrap() + .send(Ok(serde_json::json!({ + "variable": variable_name, + "type": type_.unwrap().get("value").unwrap(), + "value": value.unwrap().get("value").unwrap(), + }))) + .unwrap(); + }); + } + + /// Perform setup of listening and forwarding of stdin and return a sender that will forward to the + /// stdin of a process. + fn setup_stdin( + mut child_stdin: ChildStdin, + analyser: Arc>, + ) -> mpsc::Sender { + let (stdin_tx, mut stdin_rx) = mpsc::channel(32); + let tx = stdin_tx.clone(); + + tokio::spawn(async move { + let tokio_stdin = tokio_io::stdin(); + let mut reader = FramedRead::new(tokio_stdin, BytesCodec::new()); + while let Some(line) = reader.next().await { + analyser + .lock() + .unwrap() + .set_status(PDBStatus::Processing(Message::Custom)); + let buf = line.unwrap().freeze(); + tx.send(buf).await.unwrap(); + } + }); + + tokio::spawn(async move { + while let Some(text) = stdin_rx.recv().await { + match child_stdin.write(&text).await { + Ok(_) => {} + Err(e) => { + eprintln!("Writing stdin err e: {}", e); + } + }; + } + }); + + stdin_tx + } + + /// Perform setup of reading PDB stdout, analysing it and writing it back to stdout. + fn setup_stdout(stdout: ChildStdout, analyser: Arc>) { + tokio::spawn(async move { + let mut reader = FramedRead::new(stdout, BytesCodec::new()); + while let Some(Ok(text)) = reader.next().await { + analyser.lock().unwrap().handle_output(text); + } + }); + } +} + +#[derive(Debug)] +pub struct PDBAnalyser { + pdb_status: PDBStatus, + process_pid: String, + awakener: Option>>, + notifier_tx: mpsc::Sender, + print_variable_value: String, + reporting_location: bool, +} + +impl PDBAnalyser { + pub fn new(notifier_tx: mpsc::Sender, process_pid: String) -> Self { + PDBAnalyser { + pdb_status: PDBStatus::Processing(Message::Launching), + process_pid, + // Gets set when trying to listen for when it's ready to process commands + awakener: None, + notifier_tx, + print_variable_value: "".to_string(), + reporting_location: true, + } + } + + pub fn handle_output(&mut self, bytes: BytesMut) { + let text = String::from_utf8_lossy(&bytes[..]).to_string(); + print!("{}", text); + io::stdout().flush().unwrap(); + + match &self.pdb_status { + PDBStatus::Processing(msg) => match msg { + Message::PrintVariable(var) => { + let text = self.print_variable_value.clone() + + &self.strip_gibberish(&String::from_utf8_lossy(&bytes[..]).to_string()); + + let mut from = 0; + let mut to = text.len(); + + let prefix = format!("print({})\r\n", var.name()); + let prefix_length = prefix.len(); + if to >= prefix_length && &text[0..prefix_length] == prefix { + from += prefix_length; + } + + let suffix = "\r\n(Pdb) "; + let suffix_length = suffix.len(); + if to >= suffix_length && &text[to - suffix_length..to] == suffix { + to -= suffix_length; + } + + self.print_variable_value = text[from..to].to_string(); + } + _ => {} + }, + _ => {} + } + + for line in text.split("\r\n") { + match &self.pdb_status { + PDBStatus::Listening => {} + PDBStatus::Processing(msg) => match msg { + Message::Restart | Message::Launching => { + self.check_location(&line[..]); + } + Message::Breakpoint(_) => { + self.check_breakpoint(&line[..]); + } + Message::StepIn(_) | Message::StepOver(_) | Message::Continue => { + self.check_location(&line[..]); + self.check_returning(&line[..]); + self.check_exited(&line[..]); + } + Message::Custom => { + self.check_breakpoint(&line[..]); + self.check_location(&line[..]); + self.check_returning(&line[..]); + self.check_exited(&line[..]); + } + _ => {} + }, + }; + } + + match text.split("\r\n").last() { + Some(l) => { + if str::ends_with(l, "(Pdb) ") { + match &self.pdb_status { + PDBStatus::Processing(msg) => match msg { + Message::PrintVariable(var) => { + let tx_option = self.awakener.take(); + match tx_option { + Some(tx) => { + // TODO: Error checks for proper thing + if self.print_variable_value + == format!( + "error: no variable named '{}' found in this frame", + var.name() + ) + || self.print_variable_value == "error: invalid process" + { + tx.send(Err(PadreError::new( + PadreErrorKind::DebuggerError, + "Variable not found".to_string(), + format!("Variable '{}' not found", var.name()), + ))) + .unwrap() + } else { + tx.send(Ok(serde_json::json!({ + "value": self.print_variable_value, + }))) + .unwrap(); + self.print_variable_value = "".to_string(); + } + } + _ => {} + } + } + _ => {} + }, + _ => {} + }; + self.set_status(PDBStatus::Listening); + } + } + None => {} + } + } + + /// We get some kind of terminal this weird pattern out of Python 3.9 on + /// a mac sometimes: + /// \r\n\x1b[?2004h(Pdb) + /// Patterns we strip at present: + /// \x1b[?2004h + /// \x1b[?2004l\r + /// Stripping it out for now, not ideal but will have to do for now. + fn strip_gibberish(&self, text: &str) -> String { + lazy_static! { + static ref RE_GIBBERISH: Regex = Regex::new(r#"(.*)\x1b\[\?2004[a-z]\r?(.*)"#).unwrap(); + } + + let mut ret1 = "".to_string(); + + let split1: Vec<&str> = text.split("\x1b[?2004h").collect(); + for s in split1 { + ret1 += s + } + + let mut ret2 = "".to_string(); + + let split2: Vec<&str> = ret1.split("\x1b[?2004l\r").collect(); + for s in split2 { + ret2 += s + } + + ret2 + } + + pub fn set_status(&mut self, pdb_status: PDBStatus) { + match &self.pdb_status { + PDBStatus::Listening => {} + _ => { + match pdb_status { + PDBStatus::Listening => { + let tx_option = self.awakener.take(); + match tx_option { + Some(tx) => { + tx.send(Ok(serde_json::json!({}))).unwrap(); + } + _ => {} + }; + } + _ => {} + }; + } + } + self.pdb_status = pdb_status; + } + + /// Sets up the analyser ready for analysing the message. + /// + /// It sets the status of the analyser to Processing for that message and if given + /// it marks the analyser to send a message to `tx_done` to indicate when the + /// message is processed. + pub fn analyse_message( + &mut self, + msg: Message, + tx_done: Option>>, + ) { + self.pdb_status = PDBStatus::Processing(msg); + self.awakener = tx_done; + } + + fn check_breakpoint(&self, line: &str) { + lazy_static! { + static ref RE_BREAKPOINT: Regex = + Regex::new("Breakpoint (\\d*) at (.*):(\\d*)$").unwrap(); + } + + for l in line.split("\r") { + for cap in RE_BREAKPOINT.captures_iter(l) { + let file = cap[2].to_string(); + let line = cap[3].parse::().unwrap(); + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Breakpoint set at file {} and line number {}", file, line), + ); + } + } + } + + fn check_location(&self, line: &str) { + if !self.reporting_location { + return; + } + + lazy_static! { + static ref RE_JUMP_TO_POSITION: Regex = + Regex::new("^> (.*)\\((\\d*)\\)[<>\\w]*\\(\\)$").unwrap(); + } + + for cap in RE_JUMP_TO_POSITION.captures_iter(line) { + let file = cap[1].to_string(); + let line = cap[2].parse::().unwrap(); + jump_to_position(self.notifier_tx.clone(), &file, line); + } + } + + fn check_returning(&self, line: &str) { + if !self.reporting_location { + return; + } + + lazy_static! { + static ref RE_RETURNING: Regex = + Regex::new("^> (.*)\\((\\d*)\\)[<>\\w]*\\(\\)->(.*)$").unwrap(); + } + + for cap in RE_RETURNING.captures_iter(line) { + let file = cap[1].to_string(); + let line = cap[2].parse::().unwrap(); + let return_value = cap[3].to_string(); + jump_to_position(self.notifier_tx.clone(), &file, line); + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!("Returning value {}", return_value), + ); + } + } + + fn check_exited(&mut self, line: &str) { + lazy_static! { + static ref RE_PROCESS_EXITED: Regex = + Regex::new("^The program finished and will be restarted$").unwrap(); + static ref RE_PROCESS_EXITED_WITH_CODE: Regex = + Regex::new("^The program exited via sys.exit\\(\\)\\. Exit status: (-?\\d*)$") + .unwrap(); + } + + let exited = |exit_code| { + log_msg( + self.notifier_tx.clone(), + LogLevel::INFO, + &format!( + "Process {} exited with exit code {}", + self.process_pid, exit_code + ), + ); + }; + + for _ in RE_PROCESS_EXITED.captures_iter(line) { + &exited(0); + } + + for cap in RE_PROCESS_EXITED_WITH_CODE.captures_iter(line) { + let exit_code = cap[1].parse::().unwrap(); + &exited(exit_code); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_get_args_basic_command() { + let args = get_python_args("/usr/bin/python3", vec!["test.py", "arg1"]).unwrap(); + assert_eq!(args, vec!["-m", "pdb", "--", "test.py", "arg1"]); + } + + #[test] + fn check_get_args_recognises_matching_python_command() { + let args = get_python_args("/usr/bin/python3", vec!["python3", "test.py", "arg1"]).unwrap(); + assert_eq!(args, vec!["-m", "pdb", "test.py", "arg1"]); + } + + #[test] + fn check_get_args_leaves_non_matching_python_command() { + let args = + get_python_args("/usr/bin/python3.7", vec!["python3", "test.py", "arg1"]).unwrap(); + assert_eq!(args, vec!["-m", "pdb", "--", "python3", "test.py", "arg1"]); + } + + #[test] + fn check_get_args_accepts_module_running() { + let args = get_python_args( + "/usr/bin/python3", + vec!["python3", "-m", "abc", "--", "arg1"], + ) + .unwrap(); + assert_eq!(args, vec!["-m", "pdb", "-m", "abc", "--", "arg1"]); + } + + #[test] + fn check_get_args_accepts_command_arguments() { + let args = get_python_args( + "/usr/bin/python3", + vec!["python3", "-c", "print('Hello, World!')"], + ) + .unwrap(); + assert_eq!(args, vec!["-m", "pdb", "-c", "print('Hello, World!')"]); + } + + #[tokio::test] + async fn analyser_startup() { + let (tx, _) = mpsc::channel(1); + let mut analyser = PDBAnalyser::new(tx, "12345".to_string()); + assert_eq!( + analyser.pdb_status, + PDBStatus::Processing(Message::Launching) + ); + analyser.handle_output(BytesMut::from( + "> /Users/me/test.py(1)()\r\n-> abc = 123\r\n", + )); + assert_eq!( + analyser.pdb_status, + PDBStatus::Processing(Message::Launching) + ); + analyser.handle_output(BytesMut::from("(Pdb) ")); + assert_eq!(analyser.pdb_status, PDBStatus::Listening); + } + + #[tokio::test] + async fn analyser_custom_syntax_error() { + let (tx, _) = mpsc::channel(1); + let mut analyser = PDBAnalyser::new(tx, "12345".to_string()); + analyser.handle_output(BytesMut::from( + "> /Users/me/test.py(1)()\r\n-> abc = 123\r\n(Pdb) ", + )); + analyser.set_status(PDBStatus::Processing(Message::Custom)); + analyser.handle_output(BytesMut::from("do something nonsensical\r\n")); + assert_eq!(analyser.pdb_status, PDBStatus::Processing(Message::Custom)); + analyser.handle_output(BytesMut::from("*** SyntaxError: invalid syntax\r\n(Pdb) ")); + assert_eq!(analyser.pdb_status, PDBStatus::Listening); + } + + #[tokio::test] + async fn analyser_padre_message_wakeup() { + let (tx, _) = mpsc::channel(1); + let mut analyser = PDBAnalyser::new(tx, "12345".to_string()); + analyser.handle_output(BytesMut::from( + "> /Users/me/test.py(1)()\r\n-> abc = 123\r\n(Pdb) ", + )); + let msg = Message::Breakpoint(FileLocation::new("test.py".to_string(), 2)); + analyser.set_status(PDBStatus::Processing(msg.clone())); + assert_eq!(analyser.pdb_status, PDBStatus::Processing(msg)); + analyser.handle_output(BytesMut::from("Breakpoint 1 at test.py:2\r\n(Pdb) ")); + assert_eq!(analyser.pdb_status, PDBStatus::Listening); + } + + #[tokio::test] + async fn analyser_print_message() { + let (tx, _) = mpsc::channel(1); + let mut analyser = PDBAnalyser::new(tx, "12345".to_string()); + analyser.handle_output(BytesMut::from( + "> /Users/me/test.py(1)()\r\n-> abc = 123\r\n(Pdb) ", + )); + let msg = Message::PrintVariable(Variable::new("abc".to_string())); + analyser.set_status(PDBStatus::Processing(msg.clone())); + analyser.handle_output(BytesMut::from("print(abc)\r\n")); + analyser.handle_output(BytesMut::from("123\r\n")); + assert_eq!(analyser.print_variable_value, "123\r\n".to_string()); + analyser.handle_output(BytesMut::from("(Pdb) ")); + } +} diff --git a/padre/integration/features/basics.feature b/padre/integration/features/basics.feature index a2c62e0..7c77321 100644 --- a/padre/integration/features/basics.feature +++ b/padre/integration/features/basics.feature @@ -6,6 +6,7 @@ Feature: Basics And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I open another connection to PADRE When I open another connection to PADRE When I send a request to PADRE '{"cmd":"ping"}' on connection 0 @@ -52,78 +53,105 @@ Feature: Basics And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a raw request to PADRE 'nonsense' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Must be valid JSON"] | - | padre#debugger#Log | [5,"Can't read 'nonsense': [^ ].*"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[0,\{ | + | \}]$ | + | "debug":"Can't read 'nonsense': expected ident at line 1 column 2" | + | "error":"Must be valid JSON" | + | "status":"ERROR" | When I send a raw request to PADRE '[1,{"cmd":"no end"' And I send a raw request to PADRE ']' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Must be valid JSON"] | - | padre#debugger#Log | [5,"Can't read '\\[1,{\"cmd\":\"no end\"]': [^ ].*$"] | + # TODO: Fix the following so it can read the ID in some cases + Then I receive a raw response containing the following entries + | entry | + | ^\[0,\{ | + | \}]$ | + | "debug":"Can't read '\[1,\{\\"cmd\\":\\"no end\\"\]' | + | "error":"Must be valid JSON" | + | "status":"ERROR" | When I send a raw request to PADRE '[1,{}]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't find command"] | - | padre#debugger#Log | [5,"Can't find command '\\[1,{}\\]': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Can't find command '\[1,\{\}\]' | + | "error":"Can't find command" | + | "status":"ERROR" | When I send a raw request to PADRE '{}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read JSON"] | - | padre#debugger#Log | [5,"Can't read '{}': Must be an array"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[0,\{ | + | \}]$ | + | "debug":"Can't read '\{\}' | + | "error":"Not an array, invalid JSON" | + | "status":"ERROR" | When I send a raw request to PADRE '[]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read JSON"] | - | padre#debugger#Log | [5,"Can't read '\\[\\]': Array should have 2 elements"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[0,\{ | + | \}]$ | + | "debug":"Can't read '\[\]': Array should have 2 elements" | + | "error":"Array must have 2 elements, invalid JSON" | + | "status":"ERROR" | When I send a raw request to PADRE '["a","b"]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read id"] | - | padre#debugger#Log | [5,"Can't read '\"a\"': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[0,\{ | + | \}]$ | + | "debug":"Can't read '\\"a\\"': invalid type: string \\"a\\", expected u64" | + | "error":"Can't read id" | + | "status":"ERROR" | When I send a raw request to PADRE '[1]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read JSON"] | - | padre#debugger#Log | [5,"Can't read '\\[1\\]': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Can't read '\[1\]': Array should have 2 elements" | + | "error":"Array must have 2 elements, invalid JSON" | + | "status":"ERROR" | When I send a raw request to PADRE '[1,2]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read JSON"] | - | padre#debugger#Log | [5,"Can't read '\\[1,2\\]': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Can't read '\[1,2\]': invalid type: integer `2` | + | "error":"Can't read 2nd argument as dictionary" | + | "status":"ERROR" | When I send a raw request to PADRE '[1,{"cmd":"ping"},3]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read JSON"] | - | padre#debugger#Log | [5,"Can't read '\\[1,{\"cmd\":\"ping\"},3\\]': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Can't read '\[1,\{\\"cmd\\":\\"ping\\"\},3\]': Array should have 2 elements | + | "error":"Array must have 2 elements, invalid JSON" | + | "status":"ERROR" | When I send a request to PADRE '{"bad":"request"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't find command"] | - | padre#debugger#Log | [5,"Can't find command '\\[1,{\"bad\":\"request\"}\\]': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Can't find command '\[1,\{\\"bad\\":\\"request\\"\}\]': Need a cmd in 2nd object" | + | "error":"Can't find command" | + | "status":"ERROR" | When I send a raw request to PADRE '[1,{"cmd":{}}]' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't find command"] | - | padre#debugger#Log | [5,"Can't find command '\\[1,{\"cmd\":{}}\\]': [^ ].*$"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Can't find command '\[1,\{\\"cmd\\":\{\}\}\]': invalid type: map, expected a string" | + | "error":"Can't find command" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"not_exists"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Command unknown"] | - | padre#debugger#Log | [5,"Command unknown: 'not_exists'$"] | - #When I send a raw request to PADRE '[1,{"cmd":"ping"' - #And I send a raw request to PADRE '[1,{"cmd":"ping"}]' - #Then I receive a raw response '[1,{"ping":"pong","status":"OK"}]' - #When I send a raw request to PADRE '}]' - #Then I receive a raw response '[1,{"ping":"pong","status":"OK"}]' - #When I send a raw request to PADRE '[1,{"cmd":"ping"}][2,{"cmd":"ping"}]' - #Then I receive a raw response '[1,{"status":"OK","ping":"pong"}][2,{"status":"OK","ping":"pong"}]' - #When I send a raw request to PADRE '[3,{"cmd":"ping"}]' - #And I send a raw request to PADRE '[4,{"cmd":"ping"}]' - #Then I receive a raw response '[3,{"status":"OK","ping":"pong"}]' - #And I receive a raw response '[4,{"status":"OK","ping":"pong"}]' + Then I receive a raw response containing the following entries + | entry | + | ^\[2,\{ | + | \}]$ | + | "debug":"Command unknown: 'not_exists'" | + | "error":"Command unknown" | + | "status":"ERROR" | When I terminate padre Then padre is not running @@ -132,36 +160,55 @@ Feature: Basics And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"breakpoint"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't understand request"] | - | padre#debugger#Log | [5,"Need to specify a file name"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Need to specify a file name" | + | "error":"Can't understand request" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"breakpoint","file":"test.c"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't understand request"] | - | padre#debugger#Log | [5,"Need to specify a line number"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[2,\{ | + | \}]$ | + | "debug":"Need to specify a line number" | + | "error":"Can't understand request" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"breakpoint","file":12,"line":1}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read 'file' argument"] | - | padre#debugger#Log | [5,"Can't understand 'file': [^ ].*"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[3,\{ | + | \}]$ | + | "debug":"Can\'t understand 'file': 12" | + | "error":"Can't read 'file' argument" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"breakpoint","file":"test.c","line":"a"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't read 'line' argument"] | - | padre#debugger#Log | [5,"Can't understand 'line': [^ ].*"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[4,\{ | + | \}]$ | + | "debug":"Can't understand 'line': \\"a\\"" | + | "error":"Can\'t read 'line' argument" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"breakpoint","file":"test.c","line":12.42}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Badly specified 'line'"] | - | padre#debugger#Log | [5,"Badly specified 'line': [^ ].*"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[5,\{ | + | \}]$ | + | "debug":"Badly specified 'line': 12.42" | + | "error":"Badly specified 'line'" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"breakpoint","line":1,"file":"test.c","bad_arg":1,"bad_arg2":2}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Bad arguments"] | - | padre#debugger#Log | [5,"Bad arguments: \\[\"bad_arg\", \"bad_arg2\"\\]"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[6,\{ | + | \}]$ | + | "debug":"Bad arguments: \[\\"bad_arg\\", \\"bad_arg2\\"\]" | + | "error":"Bad arguments" | + | "status":"ERROR" | When I terminate padre Then padre is not running @@ -170,21 +217,31 @@ Feature: Basics And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"print"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't understand request"] | - | padre#debugger#Log | [5,"Need to specify a variable name"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Need to specify a variable name" | + | "error":"Can't understand request" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"print","variable":1}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Badly specified 'variable'"] | - | padre#debugger#Log | [5,"Badly specified 'variable': [^ ].*"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[2,\{ | + | \}]$ | + | "debug":"Badly specified 'variable': 1" | + | "error":"Badly specified 'variable'" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"print","variable":"a","bad_arg":1,"bad_arg2":2}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Bad arguments"] | - | padre#debugger#Log | [5,"Bad arguments: \\[\"bad_arg\", \"bad_arg2\"\\]"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[3,\{ | + | \}]$ | + | "debug":"Bad arguments: \[\\"bad_arg\\", \\"bad_arg2\\"\]" | + | "error":"Bad arguments" | + | "status":"ERROR" | When I terminate padre Then padre is not running @@ -193,41 +250,63 @@ Feature: Basics And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"getConfig"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't understand request"] | - | padre#debugger#Log | [5,"Need to specify a 'key'"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[1,\{ | + | \}]$ | + | "debug":"Need to specify a 'key'" | + | "error":"Can't understand request" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"getConfig","key":123}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Badly specified string 'key'"] | - | padre#debugger#Log | [5,"Badly specified string 'key': 123"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[2,\{ | + | \}]$ | + | "debug":"Badly specified string 'key': 123" | + | "error":"Badly specified string 'key'" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"setConfig"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't understand request"] | - | padre#debugger#Log | [5,"Need to specify a 'key'"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[3,\{ | + | \}]$ | + | "debug":"Need to specify a 'key'" | + | "error":"Can't understand request" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"setConfig","key":"test"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Can't understand request"] | - | padre#debugger#Log | [5,"Need to specify a 'value'"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[4,\{ | + | \}]$ | + | "debug":"Need to specify a 'value'" | + | "error":"Can't understand request" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"setConfig","key":123,"value":123}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Badly specified string 'key'"] | - | padre#debugger#Log | [5,"Badly specified string 'key': 123"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[5,\{ | + | \}]$ | + | "debug":"Badly specified string 'key': 123" | + | "error":"Badly specified string 'key'" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"setConfig","key":"test","value":"123"}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Badly specified 64-bit integer 'value'"] | - | padre#debugger#Log | [5,"Badly specified 64-bit integer 'value': \"123\""] | + Then I receive a raw response containing the following entries + | entry | + | ^\[6,\{ | + | \}]$ | + | "debug":"Badly specified 64-bit integer 'value': \\"123\\"" | + | "error":"Badly specified 64-bit integer 'value'" | + | "status":"ERROR" | When I send a request to PADRE '{"cmd":"setConfig","key":"test","value":123123123123123123123123123123123123123}' - Then I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Badly specified 64-bit integer 'value'"] | - | padre#debugger#Log | [5,"Badly specified 64-bit integer 'value': 1.2312312312312312e38"] | + Then I receive a raw response containing the following entries + | entry | + | ^\[7,\{ | + | \}]$ | + | "debug":"Badly specified 64-bit integer 'value': 1.2312312312312312e38" | + | "error":"Badly specified 64-bit integer 'value'" | + | "status":"ERROR" | When I open another connection to PADRE When I send a request to PADRE '{"cmd":"getConfig","key":"BackPressure"}' on connection 0 Then I receive a response '{"status":"OK","value":20}' on connection 0 @@ -237,3 +316,5 @@ Feature: Basics Then I receive a response '{"status":"OK","value":25}' on connection 0 When I send a request to PADRE '{"cmd":"getConfig","key":"BackPressure"}' on connection 1 Then I receive a response '{"status":"OK","value":20}' on connection 1 + When I terminate padre + Then padre is not running diff --git a/padre/integration/features/lldb.feature b/padre/integration/features/lldb.feature index f5994a7..561cb1f 100644 --- a/padre/integration/features/lldb.feature +++ b/padre/integration/features/lldb.feature @@ -1,31 +1,33 @@ Feature: LLDB - Debug with PADRE for a program needing LLDB + Debug with PADRE a program needing LLDB Scenario: Debug a basic program with LLDB using the LLDB command line Given that we have a file 'test_prog.c' And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a command 'b main' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.c$", 22] | + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*22"] | When I send a command 'run' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 22] | + | function | args | + | padre#debugger#Log | [4,"Process \\d+ launched"] | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | When I send a command 's' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 8] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",8] | When I send a command 'n' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 9] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",9] | When I send a command 'c' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#ProcessExited | [0,"\\d+"] | + | function | args | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | When I terminate padre Then padre is not running @@ -34,21 +36,23 @@ Feature: LLDB And I have compiled the test program 'test_prog.c' with compiler '' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"breakpoint","file":"test_prog.c","line":17}' Then I receive both a response '{"status":"OK"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4, ".*test_prog.c.*17"] | - | padre#debugger#BreakpointSet | [".*test_prog.c$", 17] | + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*17"] | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.c.*17"] | When I send a request to PADRE '{"cmd":"breakpoint","file":"not_exists.c","line":17}' - Then I receive both a response '{"status":"PENDING"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4,".*not_exists.c.*17"] | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Setting breakpoint.*not_exists.c.*17"] | + | padre#debugger#Log | [4,"Breakpoint pending"] | When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.c$",22] | - | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | - | padre#debugger#Log | [4,"Launching process"] | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | When I send a request to PADRE '{"cmd":"stepIn"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | @@ -71,10 +75,14 @@ Feature: LLDB | padre#debugger#JumpToPosition | [".*test_prog.c$",18] | When I send a request to PADRE '{"cmd":"print","variable":"a"}' Then I receive a response '{"status":"OK","variable":"a","value":"1","type":"int"}' + When I send a request to PADRE '{"cmd":"stepOver","count":3}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",10] | When I send a request to PADRE '{"cmd":"continue"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with - | function | args | - | padre#debugger#ProcessExited | [0,"\\d+"] | + | function | args | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | When I terminate padre Then padre is not running @@ -83,47 +91,103 @@ Feature: LLDB | gcc -g -O0 | | clang -g -O0 | + Scenario: Debug a basic program by setting multiple breakpoints immediately + Given that we have a file 'test_prog.c' + And I have compiled the test program 'test_prog.c' with compiler 'clang -g -O0' to program 'test_prog' + And that we have a test program 'test_prog' that runs with 'lldb' debugger + When I debug the program with PADRE + # Purposefully don't give it a chance to start + When I send a request to PADRE '{"cmd":"breakpoint","file":"test_prog.c","line":8}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*8"] | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.c.*8"] | + When I send a request to PADRE '{"cmd":"breakpoint","file":"test_prog.c","line":13}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*13"] | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.c.*13"] | + When I send a request to PADRE '{"cmd":"breakpoint","file":"test_prog.c","line":17}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*17"] | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.c.*17"] | + When I send a request to PADRE '{"cmd":"breakpoint","file":"test_prog.c","line":18}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*18"] | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.c.*18"] | + When I send a request to PADRE '{"cmd":"unbreakpoint","file":"test_prog.c","line":18}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Removed breakpoint.*test_prog.c.*18"] | + When I send a request to PADRE '{"cmd":"run"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | + When I send a request to PADRE '{"cmd":"continue"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",8] | + When I send a request to PADRE '{"cmd":"continue"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",13] | + When I send a request to PADRE '{"cmd":"continue"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",17] | + When I send a request to PADRE '{"cmd":"continue"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | + When I terminate padre + Then padre is not running + Scenario: Debug a basic program with LLDB using the both the LLDB command line and the PADRE connection Given that we have a file 'test_prog.c' And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a command 'b func3' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.c$", 17] | + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.c.*17"] | When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.c$",22] | - | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | - | padre#debugger#Log | [4,"Launching process"] | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | When I send a command 's' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 8] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",8] | When I send a request to PADRE '{"cmd":"stepOver"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | | padre#debugger#JumpToPosition | [".*test_prog.c$",9] | When I send a command 'n' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 17] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",17] | When I send a command 'n' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 18] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",18] | When I send a request to PADRE '{"cmd":"print","variable":"a"}' Then I receive a response '{"status":"OK","variable":"a","value":"1","type":"int"}' When I send a command 'c' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.c$", 10] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",10] | When I send a request to PADRE '{"cmd":"continue"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with - | function | args | - | padre#debugger#ProcessExited | [0,"\\d+"] | + | function | args | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | When I terminate padre Then padre is not running @@ -132,21 +196,34 @@ Feature: LLDB And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"stepIn"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with - | function | args | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | | padre#debugger#Log | [3,"No process running"] | When I send a request to PADRE '{"cmd":"stepOver"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with - | function | args | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | | padre#debugger#Log | [3,"No process running"] | When I send a request to PADRE '{"cmd":"continue"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with - | function | args | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | | padre#debugger#Log | [3,"No process running"] | When I send a request to PADRE '{"cmd":"print","variable":"a"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with - | function | args | + Then I receive a response '{"status":"ERROR","error":"Variable not found","debug":"Variable 'a' not found"}' + When I send a request to PADRE '{"cmd":"run"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | + When I send a request to PADRE '{"cmd":"continue"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | + When I send a request to PADRE '{"cmd":"continue"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | | padre#debugger#Log | [3,"No process running"] | When I terminate padre Then padre is not running @@ -156,29 +233,34 @@ Feature: LLDB And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' And that we have a test program 'test_prog' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.c$",22] | - | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | - | padre#debugger#Log | [4,"Launching process"] | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | + When I send a request to PADRE '{"cmd":"run"}' + Then I receive both a response '{"status":"ERROR","error":"Process already running","debug":"Process with pid '\\d+' already running"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Launching process"] | When I send a request to PADRE '{"cmd":"print","variable":"a"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [3,"variable 'a' doesn't exist here"] | + Then I receive a response '{"status":"ERROR","error":"Variable not found","debug":"Variable 'a' not found"}' When I terminate padre Then padre is not running - Scenario: Printing variables in rust + Scenario: Debugging rust Given that we have a file 'test_print_variables.rs' And I have compiled the test program 'test_print_variables.rs' with compiler 'rustc -g' to program 'test_print_variables' And that we have a test program 'test_print_variables' that runs with 'lldb' debugger When I debug the program with PADRE + And I give PADRE chance to start When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with + Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | | padre#debugger#Log | [3,"Stopped at unknown position"] | | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | When I send a request to PADRE '{"cmd":"continue"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | @@ -193,76 +275,73 @@ Feature: LLDB Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",18] | - #When I send a request to PADRE '{"cmd":"print","variable":"b"}' - #Then I receive a response '{"status":"OK","variable":"b","deref":{"variable":"\\*b","type":"int","value":"42"},"type":"int \\*","value":"^&0x[0-9a-f]*$"}' - #When I send a request to PADRE '{"cmd":"stepOver"}' - #Then I receive both a response '{"status":"OK"}' and I expect to be called with - # | function | args | - # | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",19] | - #When I send a request to PADRE '{"cmd":"print","variable":"a"}' - #Then I receive a response '{"status":"OK","variable":"a","value":"^42.[0-9][0-9]*$","type":"float"}' - #When I send a request to PADRE '{"cmd":"stepOver"}' - #Then I receive both a response '{"status":"OK"}' and I expect to be called with - # | function | args | - # | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",20] | - #When I send a request to PADRE '{"cmd":"print","variable":"a"}' - #Then I receive a response '{"status":"OK","variable":"a","value":"true","type":"bool"}' - #When I send a request to PADRE '{"cmd":"stepOver"}' - #Then I receive both a response '{"status":"OK"}' and I expect to be called with - # | function | args | - # | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",21] | - #When I send a request to PADRE '{"cmd":"print","variable":"a"}' - #Then I receive a response '{"status":"OK","variable":"a","value":"TEST","type":"&str"}' - #When I send a request to PADRE '{"cmd":"stepOver"}' - #Then I receive both a response '{"status":"OK"}' and I expect to be called with - # | function | args | - # | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",22] | - #When I send a request to PADRE '{"cmd":"print","variable":"b"}' - #Then I receive a response '{"status":"OK","variable":"b","deref":{"variable":"\\*b","type":"&str","value":"TEST"},"type":"&str *","value":"^&0x[0-9a-f]*$"}' + When I send a request to PADRE '{"cmd":"print","variable":"b"}' + Then I receive a response '{"status":"OK","variable":"b","type":"int \\*","value":"^0x[0-9a-f]*$"}' + When I send a request to PADRE '{"cmd":"print","variable":"*b"}' + Then I receive a response '{"status":"OK","variable":"b","type":"int","value":"42"}' + When I send a request to PADRE '{"cmd":"stepOver"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",19] | + When I send a request to PADRE '{"cmd":"print","variable":"a"}' + Then I receive a response '{"status":"OK","variable":"a","value":"^42.[0-9][0-9]*$","type":"float"}' + When I send a request to PADRE '{"cmd":"stepOver"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",20] | + When I send a request to PADRE '{"cmd":"print","variable":"a"}' + Then I receive a response '{"status":"OK","variable":"a","value":"true","type":"bool"}' + When I send a request to PADRE '{"cmd":"stepOver"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",21] | + When I send a request to PADRE '{"cmd":"print","variable":"a"}' + Then I receive a response '{"status":"OK","variable":"a","value":"TEST","type":"&str"}' + When I send a request to PADRE '{"cmd":"stepOver"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_print_variables.rs$",22] | + When I send a request to PADRE '{"cmd":"print","variable":"b"}' + Then I receive a response '{"status":"OK","variable":"b","type":"&str *","value":"^0x[0-9a-f]*$"}' When I terminate padre Then padre is not running Scenario: Test spawning process timeout Given that we have a file 'test_prog.c' And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' - And that we have a test program 'test_prog' that runs with './test_files/lldb_spawn_timeout.py' debugger of type 'lldb' + And that we have a test program 'test_prog' that runs with '`pwd`/test_files/lldb_spawn_timeout.py' debugger of type 'lldb' When I debug the program with PADRE When I send a request to PADRE '{"cmd":"setConfig","key":"ProcessSpawnTimeout","value":1}' Then I receive a response '{"status":"OK"}' When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with + Then I receive both a response '{"status":"ERROR","debug":"Process spawning timed out after .*","error":"Timed out spawning process"}' and I expect to be called with | function | args | | padre#debugger#Log | [4,"Launching process"] | - | padre#debugger#BreakpointSet | ["test.c",25] | - | padre#debugger#Log | [2,"Timed out spawning process"] | Scenario: Test breakpoint timeout Given that we have a file 'test_prog.c' And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' - And that we have a test program 'test_prog' that runs with './test_files/lldb_breakpoint_timeout.py' debugger of type 'lldb' + And that we have a test program 'test_prog' that runs with '`pwd`/test_files/lldb_breakpoint_timeout.py' debugger of type 'lldb' When I debug the program with PADRE When I send a request to PADRE '{"cmd":"setConfig","key":"BreakpointTimeout","value":1}' Then I receive a response '{"status":"OK"}' When I send a request to PADRE '{"cmd":"breakpoint","file":"test.c","line":17}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with + Then I receive both a response '{"status":"ERROR","debug":"Breakpoint setting timed out after .*","error":"Timed out setting breakpoint"}' and I expect to be called with | function | args | | padre#debugger#Log | [4,"Setting breakpoint in file test.c at line number 17"] | - | padre#debugger#Log | [2,"Timed out setting breakpoint"] | Scenario: Test print timeout Given that we have a file 'test_prog.c' And I have compiled the test program 'test_prog.c' with compiler 'gcc -g -O0' to program 'test_prog' - And that we have a test program 'test_prog' that runs with './test_files/lldb_print_variable_timeout.py' debugger of type 'lldb' + And that we have a test program 'test_prog' that runs with '`pwd`/test_files/lldb_print_variable_timeout.py' debugger of type 'lldb' When I debug the program with PADRE When I send a request to PADRE '{"cmd":"setConfig","key":"PrintVariableTimeout","value":1}' Then I receive a response '{"status":"OK"}' When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.c$",22] | - | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | - | padre#debugger#Log | [4,"Launching process"] | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"Process \\d+ launched"] | + | padre#debugger#JumpToPosition | [".*test_prog.c$",22] | When I send a request to PADRE '{"cmd":"print","variable":"a"}' - Then I receive both a response '{"status":"ERROR"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [2,"Timed out printing variable"] | + Then I receive a response '{"status":"ERROR","debug":"Printing variable timed out after .*","error":"Timed out printing variable"}' diff --git a/padre/integration/features/nodejs.feature b/padre/integration/features/nodejs.feature index 0235154..a184413 100644 --- a/padre/integration/features/nodejs.feature +++ b/padre/integration/features/nodejs.feature @@ -5,17 +5,20 @@ Feature: NodeJS Given that we have a test program './test_files/test_prog.js' that runs with 'node' debugger When I debug the program with PADRE When I send a request to PADRE '{"cmd":"breakpoint","file":"test_files/test_prog.js","line":16}' - Then I receive a response '{"status":"PENDING"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Breakpoint pending.*test_prog.js.*16"] | When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4,"Launching process"] | - | padre#debugger#JumpToPosition | [".*test_prog.js",22] | - | padre#debugger#BreakpointSet | [".*test_prog.js",16] | + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Launching process"] | + | padre#debugger#Log | [4,"^.* launched .*$"] | + | padre#debugger#JumpToPosition | [".*test_prog.js",22] | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.js.*16"] | When I send a request to PADRE '{"cmd":"breakpoint","file":"test_files/test_prog.js","line":19}' Then I receive both a response '{"status":"OK"}' and I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.js",19] | + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.js.*19"] | When I send a request to PADRE '{"cmd":"stepOver"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | diff --git a/padre/integration/features/python.feature b/padre/integration/features/python.feature index 5f07e53..7a5263b 100644 --- a/padre/integration/features/python.feature +++ b/padre/integration/features/python.feature @@ -2,77 +2,103 @@ Feature: Python Debug with PADRE a Python program Scenario: Debug a basic program with Python using the Python debugger command line - Given that we have a test program './test_files/test_prog.py' that runs with 'python3' debugger of type 'python' + Given that we have a file 'test_prog.py' + And that we have a test program 'test_prog.py' that runs with 'python3' debugger of type 'python' When I debug the program with PADRE - When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4,"Launching process"] | - | padre#debugger#JumpToPosition | [".*test_prog.py",3] | + And I give PADRE chance to start + Then I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py", 1] | When I send a command 'b a' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#BreakpointSet | [".*test_prog.py$", 20] | + | function | args | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.py.*15"] | When I send a command 's' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py$", 6] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py$",5] | When I send a command 'n' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py$", 10] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py$",11] | When I send a command 'c' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py$", 21] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py$",16] | When I send a command 'c' using the terminal Then I expect to be called with - | function | args | - | padre#debugger#ProcessExited | [0,"\\d+"] | - | padre#debugger#JumpToPosition | [".*test_prog.py$", 3] | + | function | args | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | + | padre#debugger#JumpToPosition | [".*test_prog.py$",1] | When I terminate padre Then padre is not running Scenario: Debug a basic program with Python using the PADRE interface - Given that we have only a test program './test_files/test_prog.py' + Given that we have a file 'test_prog.py' + And that we have only a test program 'test_prog.py' When I debug the program with PADRE - When I send a request to PADRE '{"cmd":"breakpoint","file":"`pwd`/test_files/test_prog.py","line":21}' - Then I receive both a response '{"status":"PENDING"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4, ".*test_prog.py.*21"] | + And I give PADRE chance to start + Then I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py", 1] | + When I send a request to PADRE '{"cmd":"breakpoint","file":"`test_dir`/test_prog.py","line":16}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.py.*16"] | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.py.*16"] | + When I send a request to PADRE '{"cmd":"breakpoint","file":"`test_dir`/test_prog.py","line":18}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.py.*18"] | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.py.*18"] | + When I send a request to PADRE '{"cmd":"unbreakpoint","file":"`test_dir`/test_prog.py","line":18}' + Then I receive a response '{"status":"OK"}' When I send a request to PADRE '{"cmd":"run"}' - Then I receive both a response '{"status":"OK","pid":"\\d+"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4,"Launching process"] | - | padre#debugger#JumpToPosition | [".*test_prog.py",3] | - | padre#debugger#BreakpointSet | [".*test_prog.py",21] | - When I send a request to PADRE '{"cmd":"breakpoint","file":"`pwd`/test_files/test_prog.py","line":22}' Then I receive both a response '{"status":"OK"}' and I expect to be called with - | function | args | - | padre#debugger#Log | [4, ".*test_prog.py.*22"] | - | padre#debugger#BreakpointSet | [".*test_prog.py",22] | + | function | args | + | padre#debugger#Log | [4,"Restarting process"] | + | padre#debugger#JumpToPosition | [".*test_prog.py",1] | + When I send a request to PADRE '{"cmd":"breakpoint","file":"`test_dir`/test_prog.py","line":17}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#Log | [4,"Setting breakpoint.*test_prog.py.*17"] | + | padre#debugger#Log | [4,"Breakpoint set.*test_prog.py.*17"] | When I send a request to PADRE '{"cmd":"stepOver"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py",6] | + | padre#debugger#JumpToPosition | [".*test_prog.py",5] | When I send a request to PADRE '{"cmd":"continue"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py",21] | + | padre#debugger#JumpToPosition | [".*test_prog.py",16] | When I send a request to PADRE '{"cmd":"stepIn"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py",6] | + | padre#debugger#JumpToPosition | [".*test_prog.py",1] | + When I send a request to PADRE '{"cmd":"stepOver"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py",2] | + When I send a request to PADRE '{"cmd":"stepOver"}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py",2] | + | padre#debugger#Log | [4,"Returning.*'test string'"] | When I send a request to PADRE '{"cmd":"continue"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with | function | args | - | padre#debugger#JumpToPosition | [".*test_prog.py",22] | + | padre#debugger#JumpToPosition | [".*test_prog.py",17] | When I send a request to PADRE '{"cmd":"print","variable":"b"}' - Then I receive a response '{"status":"OK","variable":"b","value":"123"}' + Then I receive a response '{"status":"OK","variable":"b","value":"123","type":""}' + When I send a request to PADRE '{"cmd":"stepOver","count":3}' + Then I receive both a response '{"status":"OK"}' and I expect to be called with + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py",19] | + | padre#debugger#Log | [4,"Returning.*"] | When I send a request to PADRE '{"cmd":"continue"}' Then I receive both a response '{"status":"OK"}' and I expect to be called with - | function | args | - | padre#debugger#ProcessExited | [0,"\\d+"] | - | padre#debugger#JumpToPosition | [".*test_prog.py$", 3] | + | function | args | + | padre#debugger#JumpToPosition | [".*test_prog.py",1] | + | padre#debugger#Log | [4,"Process \\d+ exited with exit code 0"] | When I terminate padre Then padre is not running diff --git a/padre/integration/features/steps/shared.py b/padre/integration/features/steps/shared.py index 2904087..b51c5d7 100644 --- a/padre/integration/features/steps/shared.py +++ b/padre/integration/features/steps/shared.py @@ -182,8 +182,6 @@ def cancel(): loop.call_at(loop.time() + TIMEOUT, cancel) - message = message.replace("`pwd`", os.getcwd()) - async def do_write(writer, message): writer.write(message.encode()) @@ -192,6 +190,16 @@ async def do_write(writer, message): future.set_result(True) +def replace_pwd_and_test_dir(tmpdir, s): + test_dir = os.path.realpath(tmpdir) + # Causes problems on a mac for testing + if test_dir.startswith("/private"): + test_dir = test_dir[8:] + s = s.replace("`pwd`", os.getcwd()) + s = s.replace("`test_dir`", test_dir) + return s + + @fixture def run_padre(context, timeout=20): """ @@ -218,7 +226,8 @@ def cancel(): args.append("--type={}".format(context.padre.program_type)) if context.padre.debugger is not None: - args.append("--debugger={}".format(context.padre.debugger)) + debugger = replace_pwd_and_test_dir(context.tmpdir.name, context.padre.debugger) + args.append("--debugger={}".format(debugger)) context.padre.process = await asyncio.create_subprocess_exec( program, @@ -226,6 +235,7 @@ def cancel(): stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, loop=loop, + cwd=os.path.realpath(context.tmpdir.name) ) line = await context.padre.process.stdout.readline() @@ -381,6 +391,24 @@ def padre_debugger(context): context.padre.get_children() +@when("I sleep for a moment") +def sleep_at_startup(context): + """ + Just sleep for a very short period of time + """ + time.sleep(0.1) + + +@when("I give PADRE chance to start") +def sleep_at_startup(context): + """ + This is a bit rubbish but it's for tests that interfere with writing to + stdin. This gives PADRE a chance to fully startup before we start confusing + it. + """ + time.sleep(2) + + @when("I open another connection to PADRE") def connect_padre(context): """ @@ -422,7 +450,7 @@ def padre_request_raw(context, request, connection): loop = asyncio.get_event_loop() future = loop.create_future() - print("Request: {}".format(request)) + request = replace_pwd_and_test_dir(context.tmpdir.name, request) loop.run_until_complete( do_send_to_padre(future, context.connections[int(connection)][1], request, loop) @@ -515,6 +543,17 @@ def padre_raw_response(context, response): assert_that(future.result()[0], equal_to(response)) +@then("I receive a raw response containing the following entries") +def padre_raw_response_containing(context): + """ + I expect the correct response to a request + """ + result = get_response(context.connections[0][0]).result()[0] + for row in context.table: + entry = row[0] + assert_that(result, matches_regexp(entry)) + + @then( "I receive both a response '{response}' and I expect to be called on connection {connection} with" ) @@ -717,6 +756,8 @@ def terminate_program(context): """ Close PADRE """ + context.padre.get_children() + subprocess.run( ["kill", "-SIGINT", "{}".format(context.padre.process.pid)], stdout=subprocess.PIPE, diff --git a/padre/integration/test_files/lldb_print_variable_timeout.py b/padre/integration/test_files/lldb_print_variable_timeout.py index 05eed89..0925bce 100755 --- a/padre/integration/test_files/lldb_print_variable_timeout.py +++ b/padre/integration/test_files/lldb_print_variable_timeout.py @@ -21,12 +21,13 @@ def main(): prog = args.prog_args[0] sys.stdout.write('(lldb) target create "{}"\n'.format(prog)) sys.stdout.write("Current executable set to '{}' (x86_64).\n".format(prog)) + sys.stdout.write("(lldb) ") sys.stdout.flush() for line in sys.stdin: line = line.rstrip() - sys.stdout.write('(lldb) {}\n'.format(line)) if re.match('settings.*', line): + sys.stdout.write("(lldb) ") sys.stdout.flush() continue @@ -34,19 +35,20 @@ def main(): sys.stdout.write("Breakpoint 1: where = a.out`main + 15 at " + "test_prog.c:22:10, address = " + "0x0000000100000f2f\n") + sys.stdout.write("(lldb) ") elif line == "process launch": sys.stdout.write("Process 12345 launched: " + "'{}' (x86_64)\n".format(prog)) sys.stdout.write("Process 12345 stopped\n") - sys.stdout.write("* thread #1, queue = 'com.apple.main-thread', " + sys.stdout.write("* thread #1, name = 'a.out', " + "stop reason = breakpoint 1.1\n") - sys.stdout.write(" frame #0 at /Users/stevent@kainos.com/code/" - + "personal/vim-padre/padre/test_prog.c:22\n") - sys.stdout.write("Target 0: (tmp) stopped.\n") + sys.stdout.write(" frame #0 at /home/me/padre/test_prog.c:22\n") + sys.stdout.write("(lldb) ") if re.match('frame variable (.*)', line): time.sleep(TIMEOUT + 1) sys.stdout.write("(int) i = 0\n") + sys.stdout.write("(lldb) ") sys.stdout.flush() diff --git a/padre/integration/test_files/lldb_spawn_timeout.py b/padre/integration/test_files/lldb_spawn_timeout.py index 5ef0b78..3de2ce7 100755 --- a/padre/integration/test_files/lldb_spawn_timeout.py +++ b/padre/integration/test_files/lldb_spawn_timeout.py @@ -31,12 +31,9 @@ def main(): sys.stdout.flush() continue - if re.match('b(reakpoint)* .*main', line): - sys.stdout.write("Breakpoint 1: where = a.out`main + 15 at " - + "test.c:25:10, address = 0x0000000100000f2f\n") - elif line == "process launch": + if line == "process launch": time.sleep(TIMEOUT + 1) - sys.stdout.write("Process 12345 launched:" + sys.stdout.write("Process 12345 launched: " + "'{}' (x86_64)\n".format(prog)) sys.stdout.write("Process 12345 stopped\n") sys.stdout.write("(lldb) ") diff --git a/padre/integration/test_files/test_prog.py b/padre/integration/test_files/test_prog.py index e17b70a..b8ace9f 100644 --- a/padre/integration/test_files/test_prog.py +++ b/padre/integration/test_files/test_prog.py @@ -1,8 +1,3 @@ -""" -Basic Python test program -""" - - def c(): return 'test string' diff --git a/padre/ptywrapper.py b/padre/ptywrapper.py new file mode 100755 index 0000000..2e989db --- /dev/null +++ b/padre/ptywrapper.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +from select import select +import datetime +import os +import pty +import signal +import sys +import tty + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +CHILD = 0 +PID = -1 +TTY_MODE = None + + +def receiveSignal(signalNumber, frame): + fid = open("/tmp/ptyout_killing", "w") + fid.write("Signal {}".format(signalNumber)) + fid.close() + + if PID != -1: + os.kill(PID, signalNumber) + + if TTY_MODE: + tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, TTY_MODE) + + sys.exit(0) + + +def _writen(fd, data): + """Write all the data to a descriptor.""" + while data: + n = os.write(fd, data) + data = data[n:] + + +def _read(fd): + """Default read function.""" + return os.read(fd, 1024) + + +def _copy(master_fd, master_read=_read, stdin_read=_read): + """Parent copy loop. + Copies + pty master -> standard output (master_read) + standard input -> pty master (stdin_read)""" + fds = [master_fd, STDIN_FILENO] + while True: + rfds, wfds, xfds = select(fds, [], []) + if master_fd in rfds: + data = master_read(master_fd) + if not data: # Reached EOF. + fds.remove(master_fd) + else: + os.write(STDOUT_FILENO, data) + if STDIN_FILENO in rfds: + data = stdin_read(STDIN_FILENO) + if not data: + fds.remove(STDIN_FILENO) + else: + _writen(master_fd, data) + + +def spawn(argv, master_read=_read, stdin_read=_read): + """Create a spawned process.""" + global PID, TTY_MODE + + PID, master_fd = pty.fork() + + if PID == CHILD: + os.execlp(argv[0], *argv) + + # Master + signal.signal(signal.SIGHUP, receiveSignal) + signal.signal(signal.SIGINT, receiveSignal) + signal.signal(signal.SIGQUIT, receiveSignal) + signal.signal(signal.SIGILL, receiveSignal) + signal.signal(signal.SIGTRAP, receiveSignal) + signal.signal(signal.SIGABRT, receiveSignal) + signal.signal(signal.SIGBUS, receiveSignal) + signal.signal(signal.SIGFPE, receiveSignal) + #signal.signal(signal.SIGKILL, receiveSignal) + signal.signal(signal.SIGUSR1, receiveSignal) + signal.signal(signal.SIGSEGV, receiveSignal) + signal.signal(signal.SIGUSR2, receiveSignal) + signal.signal(signal.SIGPIPE, receiveSignal) + signal.signal(signal.SIGALRM, receiveSignal) + signal.signal(signal.SIGTERM, receiveSignal) + try: + mode = tty.tcgetattr(STDIN_FILENO) + tty.setraw(STDIN_FILENO) + TTY_MODE = mode + except tty.error: # This is the same as termios.error + pass + + try: + _copy(master_fd, master_read, stdin_read) + except OSError: + pass + + if TTY_MODE: + tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, TTY_MODE) + + os.close(master_fd) + return os.waitpid(PID, 0)[1] + + +spawn(sys.argv[1:]) diff --git a/padre/src/debugger.rs b/padre/src/debugger.rs deleted file mode 100644 index 7380082..0000000 --- a/padre/src/debugger.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! Debugger Module -//! -//! Main module for handling the debuggers, defines the standard versioned debugger interfaces -//! and creates the main debugger objects. - -use std::fmt::Debug; -use std::io; -use std::sync::{Arc, Mutex}; - -use crate::config::Config; -use crate::util::{file_is_binary_executable, file_is_text}; - -use tokio::prelude::*; - -mod lldb; -mod node; -mod python; - -/// Debuggers -#[derive(Debug)] -enum DebuggerType { - LLDB, - Node, - Python, -} - -/// File location -#[derive(Clone, Deserialize, Debug, PartialEq, Eq, Hash)] -pub struct FileLocation { - name: String, - line_num: u64, -} - -impl FileLocation { - pub fn new(name: String, line_num: u64) -> Self { - FileLocation { name, line_num } - } -} - -/// Variable name -#[derive(Clone, Deserialize, Debug, PartialEq, Eq, Hash)] -pub struct Variable { - name: String, -} - -impl Variable { - pub fn new(name: String) -> Self { - Variable { name } - } -} - -/// All debugger commands -#[derive(Clone, Deserialize, Debug, PartialEq)] -pub enum DebuggerCmd { - V1(DebuggerCmdV1), -} - -/// All V1 debugger commands -#[derive(Clone, Deserialize, Debug, PartialEq)] -pub enum DebuggerCmdV1 { - Run, - Breakpoint(FileLocation), - StepIn, - StepOver, - Continue, - Print(Variable), -} - -#[derive(Debug)] -pub struct Debugger { - debugger: Box, -} - -impl Debugger { - pub fn new(debugger: Box) -> Debugger { - Debugger { debugger } - } - - pub fn stop(&mut self) { - self.debugger.teardown(); - } - - pub fn handle_v1_cmd( - &mut self, - cmd: &DebuggerCmdV1, - config: Arc>, - ) -> Box + Send> { - match cmd { - DebuggerCmdV1::Run => self.debugger.run(config), - DebuggerCmdV1::Breakpoint(fl) => self.debugger.breakpoint(fl, config), - DebuggerCmdV1::StepIn => self.debugger.step_in(), - DebuggerCmdV1::StepOver => self.debugger.step_over(), - DebuggerCmdV1::Continue => self.debugger.continue_(), - DebuggerCmdV1::Print(v) => self.debugger.print(v, config), - } - } -} - -/// Debugger trait that implements the basics -pub trait DebuggerV1: Debug { - fn setup(&mut self); - fn teardown(&mut self); - fn run( - &mut self, - config: Arc>, - ) -> Box + Send>; - fn breakpoint( - &mut self, - file_location: &FileLocation, - config: Arc>, - ) -> Box + Send>; - fn step_in(&mut self) -> Box + Send>; - fn step_over(&mut self) -> Box + Send>; - fn continue_(&mut self) -> Box + Send>; - fn print( - &mut self, - variable: &Variable, - config: Arc>, - ) -> Box + Send>; -} - -/// Get the debugger implementation -/// -/// If the debugger type is not specified it will try it's best to guess what kind of debugger to -/// return. -pub fn get_debugger( - debugger_cmd: Option<&str>, - debugger_type: Option<&str>, - run_cmd: Vec, -) -> Debugger { - let debugger_type = match debugger_type { - Some(s) => match s.to_ascii_lowercase().as_str() { - "lldb" => DebuggerType::LLDB, - "python" => DebuggerType::Python, - "node" => DebuggerType::Node, - _ => panic!("Couldn't understand debugger type {}", s), - }, - None => match get_debugger_type(&run_cmd[0]) { - Some(s) => s, - None => match debugger_cmd { - Some(s) => match s { - "lldb" => DebuggerType::LLDB, - "python" | "python3" => DebuggerType::Python, - "node" => DebuggerType::Node, - _ => panic!( - "Can't find debugger type for {}, try specifying with -d or -t", - s - ), - }, - None => panic!("Can't find debugger type, try specifying with -d or -t"), - }, - }, - }; - - let debugger_cmd = match debugger_cmd { - Some(s) => s.to_string(), - None => match debugger_type { - DebuggerType::LLDB => "lldb".to_string(), - DebuggerType::Node => "node".to_string(), - DebuggerType::Python => "python3".to_string(), - }, - }; - - let mut debugger: Box = match debugger_type { - DebuggerType::LLDB => Box::new(lldb::ImplDebugger::new(debugger_cmd, run_cmd)), - DebuggerType::Node => Box::new(node::ImplDebugger::new(debugger_cmd, run_cmd)), - DebuggerType::Python => Box::new(python::ImplDebugger::new(debugger_cmd, run_cmd)), - }; - - debugger.setup(); - - Debugger::new(debugger) -} - -/// Guesses the debugger type -fn get_debugger_type(run_cmd: &str) -> Option { - if is_node(&run_cmd) { - Some(DebuggerType::Node) - } else if is_python(&run_cmd) { - Some(DebuggerType::Python) - } else if is_lldb(&run_cmd) { - Some(DebuggerType::LLDB) - } else { - None - } -} - -/// Checks if the file is a binary executable -fn is_lldb(cmd: &str) -> bool { - if file_is_binary_executable(cmd) { - return true; - } - - false -} - -/// Checks if the file is a NodeJS script -fn is_node(cmd: &str) -> bool { - if file_is_text(cmd) && cmd.ends_with(".js") { - return true; - } - - // if file_is_binary_executable(cmd) && cmd.contains("node") { - // return true; - // } - - false -} - -/// Checks if the file is a NodeJS script -fn is_python(cmd: &str) -> bool { - if file_is_text(cmd) && cmd.ends_with(".py") { - return true; - } - - // if file_is_binary_executable(cmd) && cmd.contains("python") { - // return true; - // } - - false -} diff --git a/padre/src/debugger/lldb/debugger.rs b/padre/src/debugger/lldb/debugger.rs deleted file mode 100644 index efe6730..0000000 --- a/padre/src/debugger/lldb/debugger.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! lldb client debugger -//! -//! The main LLDB Debugger entry point. Handles listening for instructions and -//! communicating through the `LLDBProcess`. - -use std::io; -use std::process::exit; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use super::process::{Event, LLDBProcess, Listener}; -use crate::config::Config; -use crate::debugger::{DebuggerV1, FileLocation, Variable}; -use crate::notifier::{log_msg, LogLevel}; - -use bytes::Bytes; -use tokio::prelude::*; -use tokio::sync::mpsc; - -#[derive(Debug)] -pub struct ImplDebugger { - process: Arc>, -} - -impl ImplDebugger { - pub fn new(debugger_cmd: String, run_cmd: Vec) -> ImplDebugger { - ImplDebugger { - process: Arc::new(Mutex::new(LLDBProcess::new(debugger_cmd, run_cmd))), - } - } -} - -impl DebuggerV1 for ImplDebugger { - /// Perform any initial setup including starting LLDB and setting up the stdio analyser stuff - /// - startup lldb and setup the stdio analyser - /// - perform initial setup so we can analyse LLDB properly - fn setup(&mut self) { - let (tx, rx) = mpsc::channel(1); - - self.process - .lock() - .unwrap() - .add_listener(Listener::LLDBLaunched, tx); - - let process = self.process.clone(); - - tokio::spawn( - rx.take(1) - .for_each(move |event| { - match event { - Event::LLDBLaunched => { - process.lock().unwrap().write_stdin(Bytes::from(&b"settings set stop-line-count-after 0\n"[..])); - process.lock().unwrap().write_stdin(Bytes::from(&b"settings set stop-line-count-before 0\n"[..])); - process.lock().unwrap().write_stdin(Bytes::from(&b"settings set frame-format frame #${frame.index}{ at ${line.file.fullpath}:${line.number}}\\n\n"[..])); - } - _ => unreachable!() - } - Ok(()) - }) - .map_err(|e| { - eprintln!("Reading stdin error {:?}", e); - }) - ); - - self.process.lock().unwrap().setup(); - } - - fn teardown(&mut self) { - self.process.lock().unwrap().teardown(); - exit(0); - } - - fn run( - &mut self, - config: Arc>, - ) -> Box + Send> { - log_msg(LogLevel::INFO, "Launching process"); - - let (tx, rx) = mpsc::channel(1); - - self.process - .lock() - .unwrap() - .add_listener(Listener::Breakpoint, tx); - - let process = self.process.clone(); - - let f = rx - .take(1) - .into_future() - .and_then(move |lldb_output| { - let lldb_output = lldb_output.0.unwrap(); - - match lldb_output { - Event::BreakpointSet(_) | Event::BreakpointMultiple => {} - _ => { - panic!("Don't understand output {:?}", lldb_output); - } - }; - - Ok(()) - }) - .and_then(move |_| { - let (tx, rx) = mpsc::channel(1); - - process - .lock() - .unwrap() - .add_listener(Listener::ProcessLaunched, tx); - - process - .lock() - .unwrap() - .write_stdin(Bytes::from("process launch\n")); - - rx.take(1).into_future() - }) - .timeout(Duration::new( - config - .lock() - .unwrap() - .get_config("ProcessSpawnTimeout") - .unwrap() as u64, - 0, - )) - .map(move |event| match event.0.unwrap() { - Event::ProcessLaunched(pid) => { - serde_json::json!({"status":"OK","pid":pid.to_string()}) - } - _ => unreachable!(), - }) - .map_err(|e| { - eprintln!("Reading stdin error {:?}", e); - io::Error::new(io::ErrorKind::Other, "Timed out spawning process") - }); - - let stmt = "breakpoint set --name main\n"; - - self.process.lock().unwrap().write_stdin(Bytes::from(stmt)); - - Box::new(f) - } - - fn breakpoint( - &mut self, - file_location: &FileLocation, - config: Arc>, - ) -> Box + Send> { - log_msg( - LogLevel::INFO, - &format!( - "Setting breakpoint in file {} at line number {}", - file_location.name, file_location.line_num - ), - ); - - let (tx, rx) = mpsc::channel(1); - - self.process - .lock() - .unwrap() - .add_listener(Listener::Breakpoint, tx); - - let f = rx - .take(1) - .into_future() - .timeout(Duration::new( - config - .lock() - .unwrap() - .get_config("BreakpointTimeout") - .unwrap() as u64, - 0, - )) - .map(move |event| match event.0.unwrap() { - Event::BreakpointSet(_) => serde_json::json!({"status":"OK"}), - Event::BreakpointPending => serde_json::json!({"status":"PENDING"}), - Event::BreakpointMultiple => serde_json::json!({"status":"OK"}), - _ => unreachable!(), - }) - .map_err(|e| { - eprintln!("Reading stdin error {:?}", e); - io::Error::new(io::ErrorKind::Other, "Timed out setting breakpoint") - }); - - let stmt = format!( - "breakpoint set --file {} --line {}\n", - file_location.name, file_location.line_num - ); - - self.process.lock().unwrap().write_stdin(Bytes::from(stmt)); - - Box::new(f) - } - - fn step_in(&mut self) -> Box + Send> { - self.step("step-in") - } - - fn step_over(&mut self) -> Box + Send> { - self.step("step-over") - } - - fn continue_(&mut self) -> Box + Send> { - self.step("continue") - } - - fn print( - &mut self, - variable: &Variable, - config: Arc>, - ) -> Box + Send> { - match self.check_process() { - Some(f) => return f, - _ => {} - } - - let (tx, rx) = mpsc::channel(1); - - self.process - .lock() - .unwrap() - .add_listener(Listener::PrintVariable, tx); - - let f = rx - .take(1) - .into_future() - .timeout(Duration::new( - config - .lock() - .unwrap() - .get_config("PrintVariableTimeout") - .unwrap() as u64, - 0, - )) - .map(move |event| match event.0.unwrap() { - Event::PrintVariable(variable, value) => serde_json::json!({ - "status": "OK", - "variable": variable.name, - "value": value.value(), - "type": value.type_() - }), - Event::VariableNotFound(variable) => { - log_msg( - LogLevel::WARN, - &format!("variable '{}' doesn't exist here", variable.name), - ); - serde_json::json!({"status":"ERROR"}) - } - _ => unreachable!(), - }) - .map_err(|e| { - eprintln!("Reading stdin error {:?}", e); - io::Error::new(io::ErrorKind::Other, "Timed out printing variable") - }); - - let stmt = format!("frame variable {}\n", variable.name); - - self.process.lock().unwrap().write_stdin(Bytes::from(stmt)); - - Box::new(f) - } -} - -impl ImplDebugger { - fn step( - &mut self, - kind: &str, - ) -> Box + Send> { - match self.check_process() { - Some(f) => return f, - _ => {} - } - - let stmt = format!("thread {}\n", kind); - - self.process.lock().unwrap().write_stdin(Bytes::from(stmt)); - - let f = future::lazy(move || { - let resp = serde_json::json!({"status":"OK"}); - Ok(resp) - }); - - Box::new(f) - } - - fn check_process( - &mut self, - ) -> Option + Send>> { - match self.process.lock().unwrap().is_process_running() { - false => { - log_msg(LogLevel::WARN, "No process running"); - let f = future::lazy(move || { - let resp = serde_json::json!({"status":"ERROR"}); - Ok(resp) - }); - - Some(Box::new(f)) - } - true => None, - } - } -} diff --git a/padre/src/debugger/lldb/mod.rs b/padre/src/debugger/lldb/mod.rs deleted file mode 100644 index c940825..0000000 --- a/padre/src/debugger/lldb/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! The LLDB debugger module - -mod debugger; -mod process; - -pub use self::debugger::ImplDebugger; diff --git a/padre/src/debugger/lldb/process.rs b/padre/src/debugger/lldb/process.rs deleted file mode 100644 index 3235273..0000000 --- a/padre/src/debugger/lldb/process.rs +++ /dev/null @@ -1,461 +0,0 @@ -//! lldb process handler -//! -//! This module performs the basic setup of and interfacing with LLDB. It will -//! analyse the output of the text and work out what is happening then. - -use std::collections::HashMap; -use std::io::BufReader; -use std::sync::{Arc, Mutex}; - -use crate::debugger::{FileLocation, Variable}; -use crate::notifier::{breakpoint_set, jump_to_position, log_msg, signal_exited, LogLevel}; -use crate::util::{check_and_spawn_process, read_output, setup_stdin}; - -use bytes::Bytes; -use regex::Regex; -use tokio::prelude::*; -use tokio::sync::mpsc::Sender; -use tokio_process::{Child, ChildStderr, ChildStdout}; - -/// You can register to listen for one of the following events: -/// - LLDBLaunched: LLDB has started up initially -/// - ProcessLaunched: LLDB has launched a process for debugging -/// - ProcessExited: The process spawned by LLDB has exited -/// - Breakpoint: A breakpoint event has happened -/// - PrintVariable: A variable has been requested to print and this is the response -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum Listener { - LLDBLaunched, - ProcessLaunched, - ProcessExited, - Breakpoint, - PrintVariable, -} - -/// An LLDB event is something that can be registered for being listened to and can be triggered -/// when these events occur such that the listener is informed of them and passed some details -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum Event { - LLDBLaunched, - // (PID) - ProcessLaunched(u64), - // (PID, Exit code) - ProcessExited(u64, i64), - BreakpointSet(FileLocation), - BreakpointMultiple, - BreakpointPending, - PrintVariable(Variable, VariableValue), - VariableNotFound(Variable), -} - -/// The value of a variable -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct VariableValue { - type_: String, - value: String, -} - -impl VariableValue { - pub fn new(type_: String, value: String) -> Self { - VariableValue { type_, value } - } - - pub fn type_(&self) -> &str { - &self.type_ - } - - pub fn value(&self) -> &str { - &self.value - } -} - -#[derive(Debug)] -pub struct LLDBProcess { - debugger_cmd: Option, - run_cmd: Option>, - lldb_process: Option, - lldb_stdin_tx: Option>, - analyser: Arc>, -} - -impl LLDBProcess { - /// Create a new LLDBProcess - pub fn new(debugger_cmd: String, run_cmd: Vec) -> Self { - LLDBProcess { - debugger_cmd: Some(debugger_cmd), - run_cmd: Some(run_cmd), - lldb_process: None, - lldb_stdin_tx: None, - analyser: Arc::new(Mutex::new(Analyser::new())), - } - } - - /// Setup LLDB - /// - /// Includes spawning the LLDB process and all the relevant stdio handlers. In particular: - /// - Sets up a `ReadOutput` from `util.rs` in order to read stdout and stderr; - /// - Sets up a thread to read stdin and forward it onto LLDB stdin; - /// - Checks that LLDB and the program to be ran both exist, otherwise panics. - pub fn setup(&mut self) { - let mut lldb_process = check_and_spawn_process( - vec![self.debugger_cmd.take().unwrap()], - self.run_cmd.take().unwrap(), - ); - - self.setup_stdout( - lldb_process - .stdout() - .take() - .expect("LLDB process did not have a handle to stdout"), - ); - self.setup_stderr( - lldb_process - .stderr() - .take() - .expect("LLDB process did not have a handle to stderr"), - ); - let stdin_tx = setup_stdin( - lldb_process - .stdin() - .take() - .expect("LLDB process did not have a handle to stdin"), - false, - ); - - self.lldb_stdin_tx = Some(stdin_tx); - self.lldb_process = Some(lldb_process); - } - - pub fn teardown(&mut self) { - self.lldb_process = None; - } - - /// Send a message to write to stdin - pub fn write_stdin(&mut self, bytes: Bytes) { - let tx = self.lldb_stdin_tx.clone(); - tokio::spawn( - tx.clone() - .unwrap() - .send(bytes) - .map(move |_| {}) - .map_err(|e| eprintln!("Error sending to LLDB: {}", e)), - ); - } - - pub fn add_listener(&mut self, kind: Listener, sender: Sender) { - self.analyser.lock().unwrap().add_listener(kind, sender); - } - - pub fn is_process_running(&self) -> bool { - self.analyser.lock().unwrap().is_process_running() - } - - /// Perform setup of reading LLDB stdout, analysing it and writing it back to stdout. - fn setup_stdout(&mut self, stdout: ChildStdout) { - let analyser = self.analyser.clone(); - tokio::spawn( - read_output(BufReader::new(stdout)) - .for_each(move |text| { - print!("{}", text); - analyser.lock().unwrap().analyse_stdout(&text); - Ok(()) - }) - .map_err(|e| eprintln!("Err reading LLDB stdout: {}", e)), - ); - } - - /// Perform setup of reading LLDB stderr, analysing it and writing it back to stdout. - fn setup_stderr(&mut self, stderr: ChildStderr) { - let analyser = self.analyser.clone(); - tokio::spawn( - read_output(BufReader::new(stderr)) - .for_each(move |text| { - eprint!("{}", text); - analyser.lock().unwrap().analyse_stderr(&text); - Ok(()) - }) - .map_err(|e| eprintln!("Err reading LLDB stderr: {}", e)), - ); - } -} - -#[derive(Debug)] -pub struct Analyser { - stdout: String, - stderr: String, - process_pid: Option, - listeners: HashMap>, -} - -impl Analyser { - pub fn new() -> Self { - Analyser { - stdout: "".to_string(), - stderr: "".to_string(), - process_pid: None, - listeners: HashMap::new(), - } - } - - pub fn add_listener(&mut self, kind: Listener, sender: Sender) { - self.listeners.insert(kind, sender); - } - - pub fn analyse_stdout(&mut self, s: &str) { - self.stdout.push_str(s); - - lazy_static! { - static ref RE_LLDB_STARTED: Regex = - Regex::new("^Current executable set to '.*' (.*)\\.$").unwrap(); - static ref RE_PROCESS_STARTED: Regex = - Regex::new("^Process (\\d+) launched: '.*' \\((.*)\\)$").unwrap(); - static ref RE_PROCESS_EXITED: Regex = - Regex::new("^Process (\\d+) exited with status = (\\d+) \\(0x[0-9a-f]*\\) *$") - .unwrap(); - static ref RE_BREAKPOINT: Regex = Regex::new( - "Breakpoint (\\d+): where = .* at (.*):(\\d+):\\d+, address = 0x[0-9a-f]*$" - ) - .unwrap(); - static ref RE_BREAKPOINT_2: Regex = - Regex::new("Breakpoint (\\d+): where = .* at (.*):(\\d+), address = 0x[0-9a-f]*$") - .unwrap(); - static ref RE_BREAKPOINT_MULTIPLE: Regex = - Regex::new("Breakpoint (\\d+): (\\d+) locations\\.$").unwrap(); - static ref RE_BREAKPOINT_PENDING: Regex = - Regex::new("Breakpoint (\\d+): no locations \\(pending\\)\\.$").unwrap(); - static ref RE_STOPPED_AT_POSITION: Regex = Regex::new(" *frame #\\d.*$").unwrap(); - static ref RE_JUMP_TO_POSITION: Regex = - Regex::new("^ *frame #\\d at (\\S+):(\\d+)$").unwrap(); - static ref RE_PRINTED_VARIABLE: Regex = - Regex::new("^\\((.*)\\) ([\\S+]*) = .*$").unwrap(); - static ref RE_PROCESS_NOT_RUNNING: Regex = - Regex::new("error: invalid process$").unwrap(); - } - - let s = self.stdout.clone(); - - for line in s.split("\n") { - for _ in RE_LLDB_STARTED.captures_iter(line) { - self.lldb_started(); - } - - for cap in RE_PROCESS_STARTED.captures_iter(line) { - let pid = cap[1].parse::().unwrap(); - self.process_started(pid); - } - - for cap in RE_PROCESS_EXITED.captures_iter(line) { - let pid = cap[1].parse::().unwrap(); - let exit_code = cap[2].parse::().unwrap(); - self.process_exited(pid, exit_code); - } - - let mut found_breakpoint = false; - - for cap in RE_BREAKPOINT.captures_iter(line) { - found_breakpoint = true; - let file = cap[2].to_string(); - let line = cap[3].parse::().unwrap(); - self.found_breakpoint(file, line); - } - - if !found_breakpoint { - for cap in RE_BREAKPOINT_2.captures_iter(line) { - found_breakpoint = true; - let file = cap[2].to_string(); - let line = cap[3].parse::().unwrap(); - self.found_breakpoint(file, line); - } - } - - if !found_breakpoint { - for _ in RE_BREAKPOINT_MULTIPLE.captures_iter(line) { - found_breakpoint = true; - self.found_multiple_breakpoints(); - } - } - - if !found_breakpoint { - for _ in RE_BREAKPOINT_PENDING.captures_iter(line) { - self.found_pending_breakpoint(); - } - } - - for _ in RE_STOPPED_AT_POSITION.captures_iter(line) { - let mut found = false; - for cap in RE_JUMP_TO_POSITION.captures_iter(line) { - found = true; - let file = cap[1].to_string(); - let line = cap[2].parse::().unwrap(); - self.jump_to_position(file, line); - } - - if !found { - self.jump_to_unknown_position(); - } - } - - for cap in RE_PRINTED_VARIABLE.captures_iter(line) { - let variable_type = cap[1].to_string(); - let variable = cap[2].to_string(); - self.printed_variable(variable, variable_type, &s); - } - - for _ in RE_PROCESS_NOT_RUNNING.captures_iter(line) { - self.process_not_running(); - } - } - - self.clear_analyser(); - } - - pub fn analyse_stderr(&mut self, s: &str) { - self.stderr.push_str(s); - - lazy_static! { - static ref RE_VARIABLE_NOT_FOUND: Regex = - Regex::new("error: no variable named '([^']*)' found in this frame$").unwrap(); - } - - let s = self.stderr.clone(); - - for line in s.split("\n") { - for cap in RE_VARIABLE_NOT_FOUND.captures_iter(line) { - let variable = cap[1].to_string(); - self.variable_not_found(variable); - } - } - - self.clear_analyser(); - } - - fn clear_analyser(&mut self) { - self.stdout = "".to_string(); - self.stderr = "".to_string(); - } - - pub fn is_process_running(&self) -> bool { - match self.process_pid { - Some(_) => true, - None => false, - } - } - - fn lldb_started(&mut self) { - match self.listeners.remove(&Listener::LLDBLaunched) { - Some(listener) => { - listener.send(Event::LLDBLaunched).wait().unwrap(); - } - None => {} - } - } - - fn process_started(&mut self, pid: u64) { - self.process_pid = Some(pid); - match self.listeners.remove(&Listener::ProcessLaunched) { - Some(listener) => { - listener.send(Event::ProcessLaunched(pid)).wait().unwrap(); - } - None => {} - } - } - - fn process_exited(&mut self, pid: u64, exit_code: i64) { - self.process_pid = None; - signal_exited(pid, exit_code); - match self.listeners.remove(&Listener::ProcessExited) { - Some(listener) => { - listener - .send(Event::ProcessExited(pid, exit_code)) - .wait() - .unwrap(); - } - None => {} - } - } - - fn found_breakpoint(&mut self, file: String, line: u64) { - breakpoint_set(&file, line); - let file_location = FileLocation::new(file, line); - match self.listeners.remove(&Listener::Breakpoint) { - Some(listener) => { - listener - .send(Event::BreakpointSet(file_location)) - .wait() - .unwrap(); - } - None => {} - } - } - - fn found_multiple_breakpoints(&mut self) { - match self.listeners.remove(&Listener::Breakpoint) { - Some(listener) => { - listener.send(Event::BreakpointMultiple).wait().unwrap(); - } - None => {} - } - } - - fn found_pending_breakpoint(&mut self) { - match self.listeners.remove(&Listener::Breakpoint) { - Some(listener) => { - listener.send(Event::BreakpointPending).wait().unwrap(); - } - None => {} - } - } - - fn jump_to_position(&mut self, file: String, line: u64) { - jump_to_position(&file, line); - } - - fn jump_to_unknown_position(&mut self) { - log_msg(LogLevel::WARN, "Stopped at unknown position"); - } - - fn printed_variable(&mut self, variable: String, variable_type: String, data: &str) { - let mut start = 1; - - while &data[start..start + 1] != ")" { - start += 1; - } - while &data[start..start + 1] != "=" { - start += 1; - } - start += 2; - - // TODO: Need a better way of doing this to strip of the last \n, - // it's possible one day we'll screw the UTF-8 pooch here. - let value = data[start..data.len() - 1].to_string(); - - match self.listeners.remove(&Listener::PrintVariable) { - Some(listener) => { - let variable = Variable::new(variable); - let value = VariableValue::new(variable_type, value); - listener - .send(Event::PrintVariable(variable, value)) - .wait() - .unwrap(); - } - None => {} - } - } - - fn process_not_running(&self) { - log_msg(LogLevel::WARN, "program not running"); - } - - fn variable_not_found(&mut self, variable: String) { - match self.listeners.remove(&Listener::PrintVariable) { - Some(listener) => { - let variable = Variable::new(variable); - listener - .send(Event::VariableNotFound(variable)) - .wait() - .unwrap(); - } - None => {} - } - } -} diff --git a/padre/src/debugger/node/analyser.rs b/padre/src/debugger/node/analyser.rs deleted file mode 100644 index 510a28d..0000000 --- a/padre/src/debugger/node/analyser.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Node message analyser -//! -//! Analyses the messages that come from the WebSocket connection to Node Debugger - -use std::sync::{Arc, Mutex}; - -use super::ws::WSHandler; -use crate::debugger::FileLocation; -use crate::notifier::{breakpoint_set, jump_to_position, log_msg, signal_exited, LogLevel}; - -use tokio::prelude::*; -use websocket::OwnedMessage; - -/// Node script, indicated by receiving a 'Debugger.scriptParsed' message from Node -#[derive(Debug, Eq, PartialEq)] -pub struct Script { - file: String, - script_id: String, - is_internal: bool, -} - -impl Script { - pub fn new(file: String, script_id: String, is_internal: bool) -> Self { - Script { - file, - script_id, - is_internal, - } - } - - pub fn get_script_id(&self) -> &str { - &self.script_id - } -} - -#[derive(Debug)] -pub struct Analyser { - scripts: Vec