├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── asmrepl.gemspec ├── bin └── asmrepl ├── lib ├── asmrepl.rb └── asmrepl │ ├── assembler.rb │ ├── disasm.rb │ ├── linux.rb │ ├── macos.rb │ ├── parser.rb │ ├── parser.tab.rb │ ├── parser.y │ ├── repl.rb │ ├── thread_state.rb │ └── version.rb └── test ├── asmrepl_test.rb └── helper.rb /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to make participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies within all project spaces, and it also applies when 50 | an individual is representing the project or its community in public spaces. 51 | Examples of representing a project or community include using an official 52 | project e-mail address, posting via an official social media account, or acting 53 | as an appointed representative at an online or offline event. Representation of 54 | a project may be further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at aaron.patterson at gmail.com. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | 79 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Aaron Patterson 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASMREPL 2 | 3 | This is a REPL for assembly language. 4 | 5 | ## Usage 6 | 7 | Install the gem: 8 | 9 | ``` 10 | $ gem install asmrepl 11 | ``` 12 | 13 | Then start the repl like this: 14 | 15 | ``` 16 | $ asmrepl 17 | ``` 18 | 19 | If you're on macOS, you'll need to start the repl with `sudo`. 20 | 21 | When the REPL starts, it will display all register values and flags: 22 | 23 | ``` 24 | ================== CPU STATE =================== 25 | rax 000000000000000000 r8 0x00007f89d0f04640 26 | rbx 000000000000000000 r9 0x0000000000000004 27 | rcx 0x00007f89d0f04a50 r10 000000000000000000 28 | rdx 0x..fc611d3f0aa2900d4 r11 0x00000001033a4000 29 | rdi 0x00007ff7bd126148 r12 000000000000000000 30 | rsi 000000000000000000 r13 0x00007ff7bd125dc0 31 | rbp 0x00007ff7bd125c40 r14 000000000000000000 32 | rsp 0x00007ff7bd125c38 r15 000000000000000000 33 | 34 | rip 0x00000001033a4001 35 | rflags 0x0000000000000246 36 | cs 0x000000000000002b 37 | fs 000000000000000000 38 | gs 000000000000000000 39 | 40 | FLAGS: ["PF", "ZF", "IF"] 41 | 42 | (rip 0x00000001033a4001)> 43 | ``` 44 | 45 | Then you can issue commands and inspect register values. Let's write to the 46 | `rax` register and inspect its value: 47 | 48 | ``` 49 | (rip 0x00000001033a4001)> mov rax, 5 50 | =============== REGISTER CHANGES =============== 51 | rax 000000000000000000 => 0x0000000000000005 52 | 53 | (rip 0x00000001033a4009)> rax 54 | 0x0000000000000005 55 | (rip 0x00000001033a4009)> 56 | ``` 57 | 58 | Now let's write to the `rbx` register and add the two values: 59 | 60 | ``` 61 | (rip 0x00000001033a4009)> mov rbx, 3 62 | =============== REGISTER CHANGES =============== 63 | rbx 000000000000000000 => 0x0000000000000003 64 | 65 | (rip 0x00000001033a4011)> add rax, rbx 66 | =============== REGISTER CHANGES =============== 67 | rax 0x0000000000000005 => 0x0000000000000008 68 | rflags 0x0000000000000246 => 0x0000000000000202 69 | 70 | FLAGS: ["IF"] 71 | 72 | (rip 0x00000001033a4015)> rax 73 | 0x0000000000000008 74 | (rip 0x00000001033a4015)> rbx 75 | 0x0000000000000003 76 | (rip 0x00000001033a4015)> 77 | ``` 78 | 79 | Finally, lets check all values in the CPU: 80 | 81 | ``` 82 | (rip 0x00000001033a4015)> cpu 83 | ================== CPU STATE =================== 84 | rax 0x0000000000000008 r8 0x00007f89d0f04640 85 | rbx 0x0000000000000003 r9 0x0000000000000004 86 | rcx 0x00007f89d0f04a50 r10 000000000000000000 87 | rdx 0x..fc611d3f0aa2900d4 r11 0x00000001033a4000 88 | rdi 0x00007ff7bd126148 r12 000000000000000000 89 | rsi 000000000000000000 r13 0x00007ff7bd125dc0 90 | rbp 0x00007ff7bd125c40 r14 000000000000000000 91 | rsp 0x00007ff7bd125c38 r15 000000000000000000 92 | 93 | rip 0x00000001033a4015 94 | rflags 0x0000000000000202 95 | cs 0x000000000000002b 96 | fs 000000000000000000 97 | gs 000000000000000000 98 | 99 | FLAGS: ["IF"] 100 | 101 | (rip 0x00000001033a4015)> 102 | ``` 103 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | require "rake/clean" 3 | 4 | ENV["MT_NO_PLUGINS"] = "1" 5 | 6 | file "lib/asmrepl/parser.tab.rb" => "lib/asmrepl/parser.y" do |t| 7 | sh "racc -l -o #{t.name} #{t.prerequisites.first}" 8 | end 9 | 10 | task :compile => "lib/asmrepl/parser.tab.rb" 11 | 12 | Rake::TestTask.new do |t| 13 | t.libs << "test" 14 | t.test_files = FileList['test/*_test.rb'] 15 | t.verbose = true 16 | t.warning = true 17 | end 18 | 19 | task :autotest do 20 | sh "fswatch -o lib test | xargs -n1 -I{} bundle exec rake test" 21 | end 22 | 23 | task :test => :compile 24 | -------------------------------------------------------------------------------- /asmrepl.gemspec: -------------------------------------------------------------------------------- 1 | $: << File.expand_path("lib") 2 | 3 | require "asmrepl/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "asmrepl" 7 | s.version = ASMREPL::VERSION 8 | s.summary = "Write assembly in a REPL!" 9 | s.description = "Tired of writing assembly and them assembling it? Now you can write assembly and evaluate it!" 10 | s.authors = ["Aaron Patterson"] 11 | s.email = "tenderlove@ruby-lang.org" 12 | s.files = `git ls-files -z`.split("\x0") 13 | s.test_files = s.files.grep(%r{^test/}) 14 | s.homepage = "https://github.com/tenderlove/asmrepl" 15 | s.license = "Apache-2.0" 16 | s.bindir = "bin" 17 | 18 | s.executables << "asmrepl" 19 | 20 | s.add_development_dependency 'minitest', '~> 5.14' 21 | s.add_development_dependency 'crabstone', '~> 4.0' 22 | s.add_development_dependency 'rake', '~> 13.0' 23 | s.add_dependency 'fisk', '~> 2.3.1' 24 | s.add_dependency 'racc', '~> 1.7.3' 25 | end 26 | -------------------------------------------------------------------------------- /bin/asmrepl: -------------------------------------------------------------------------------- 1 | #!/bin/env ruby 2 | 3 | require "asmrepl" 4 | 5 | ASMREPL::REPL.new.start 6 | -------------------------------------------------------------------------------- /lib/asmrepl.rb: -------------------------------------------------------------------------------- 1 | require "fiddle" 2 | 3 | module Fiddle 4 | unless Fiddle.const_defined?(:SIZEOF_INT64_T) 5 | SIZEOF_INT64_T = Fiddle::SIZEOF_UINTPTR_T 6 | end 7 | end 8 | 9 | require "asmrepl/parser" 10 | require "asmrepl/assembler" 11 | require "asmrepl/repl" 12 | -------------------------------------------------------------------------------- /lib/asmrepl/assembler.rb: -------------------------------------------------------------------------------- 1 | module ASMREPL 2 | class Assembler 3 | def assemble ast 4 | fisk = Fisk.new 5 | 6 | case ast 7 | in [:command, [:instruction, insn], [:register, r], [:int, n]] 8 | possibles = insn.forms.find_all do |form| 9 | form.operands.first.type == r.type 10 | end 11 | l = if possibles.any? { |form| form.operands[1].type == n.to_s } 12 | fisk.lit(n) 13 | else 14 | if r.size == 64 15 | fisk.imm32(n) 16 | else 17 | fisk.imm(n) 18 | end 19 | end 20 | fisk.gen_with_insn insn, [r, l] 21 | in [:command, [:instruction, insn], [:register, r], [:register, r2]] 22 | fisk.gen_with_insn insn, [r, r2] 23 | in [:command, [:instruction, insn], [:register, r], [:memory, mem]] 24 | fisk.gen_with_insn insn, [r, mem] 25 | in [:command, [:instruction, insn], [:memory, a], [:register, b]] 26 | fisk.gen_with_insn insn, [a, b] 27 | in [:command, [:instruction, insn], [:int, n]] 28 | forms = insn.forms 29 | 30 | l = if forms.any? { |form| form.operands[0].type == n.to_s } 31 | fisk.lit(n) 32 | else 33 | fisk.imm(n) 34 | end 35 | fisk.gen_with_insn insn, [l] 36 | 37 | in [:command, [:instruction, insn], [:register, n]] 38 | fisk.gen_with_insn insn, [n] 39 | in [:command, [:instruction, insn], [:memory, n]] 40 | fisk.gen_with_insn insn, [n] 41 | in [:command, [:instruction, insn], [:memory, n], [:int, b]] 42 | fisk.gen_with_insn insn, [n, fisk.imm(b)] 43 | in [:command, [:instruction, insn]] 44 | fisk.gen_with_insn insn, [] 45 | else 46 | p ast 47 | raise "Unknown" 48 | end 49 | 50 | fisk.to_binary 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/asmrepl/disasm.rb: -------------------------------------------------------------------------------- 1 | require "crabstone" 2 | 3 | class Crabstone::Binding::Instruction 4 | class << self 5 | alias :old_release :release 6 | end 7 | 8 | # Squelch error in crabstone 9 | def self.release obj 10 | nil 11 | end 12 | end 13 | 14 | module ASMREPL 15 | module Disasm 16 | def self.disasm buffer 17 | binary = buffer.memory[0, buffer.pos] 18 | cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) 19 | cs.disasm(binary, buffer.memory.to_i).each {|i| 20 | puts "%s %s" % [i.mnemonic, i.op_str] 21 | } 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/asmrepl/linux.rb: -------------------------------------------------------------------------------- 1 | require "fisk/helpers" 2 | require "asmrepl/thread_state" 3 | 4 | module ASMREPL 5 | module Linux 6 | include Fiddle 7 | 8 | def self.make_function name, args, ret 9 | ptr = Handle::DEFAULT[name] 10 | func = Function.new ptr, args, ret, name: name 11 | define_singleton_method name, &func.to_proc 12 | end 13 | 14 | # from sys/mman.h on macOS 15 | PROT_READ = 0x01 16 | PROT_WRITE = 0x02 17 | PROT_EXEC = 0x04 18 | MAP_PRIVATE = 0x0002 19 | MAP_SHARED = 0x0001 20 | MAP_ANON = 0x20 21 | 22 | make_function "ptrace", [TYPE_INT, TYPE_INT, TYPE_VOIDP, TYPE_VOIDP], TYPE_INT 23 | make_function "memset", [TYPE_VOIDP, TYPE_INT, TYPE_SIZE_T], TYPE_VOID 24 | 25 | make_function "mmap", [TYPE_VOIDP, 26 | TYPE_SIZE_T, 27 | TYPE_INT, 28 | TYPE_INT, 29 | TYPE_INT, 30 | TYPE_INT], TYPE_VOIDP 31 | 32 | def self.mmap_jit size 33 | ptr = mmap 0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, -1, 0 34 | ptr.size = size 35 | ptr 36 | end 37 | 38 | def self.jitbuffer size 39 | Fisk::Helpers::JITBuffer.new mmap_jit(size), size 40 | end 41 | 42 | PTRACE_TRACEME = 0 43 | 44 | PTRACE_CONT = 7 45 | 46 | # x86_64-linux-gnu/sys/ptrace.h 47 | PTRACE_GETREGS = 12 48 | PTRACE_SETREGS = 13 49 | 50 | def self.traceme 51 | raise unless ptrace(PTRACE_TRACEME, 0, 0, 0).zero? 52 | end 53 | 54 | fields = (<<-eostruct).scan(/int ([^;]*);/).flatten 55 | struct user_regs_struct 56 | { 57 | __extension__ unsigned long long int r15; 58 | __extension__ unsigned long long int r14; 59 | __extension__ unsigned long long int r13; 60 | __extension__ unsigned long long int r12; 61 | __extension__ unsigned long long int rbp; 62 | __extension__ unsigned long long int rbx; 63 | __extension__ unsigned long long int r11; 64 | __extension__ unsigned long long int r10; 65 | __extension__ unsigned long long int r9; 66 | __extension__ unsigned long long int r8; 67 | __extension__ unsigned long long int rax; 68 | __extension__ unsigned long long int rcx; 69 | __extension__ unsigned long long int rdx; 70 | __extension__ unsigned long long int rsi; 71 | __extension__ unsigned long long int rdi; 72 | __extension__ unsigned long long int orig_rax; 73 | __extension__ unsigned long long int rip; 74 | __extension__ unsigned long long int cs; 75 | __extension__ unsigned long long int eflags; 76 | __extension__ unsigned long long int rsp; 77 | __extension__ unsigned long long int ss; 78 | __extension__ unsigned long long int fs_base; 79 | __extension__ unsigned long long int gs_base; 80 | __extension__ unsigned long long int ds; 81 | __extension__ unsigned long long int es; 82 | __extension__ unsigned long long int fs; 83 | __extension__ unsigned long long int gs; 84 | }; 85 | eostruct 86 | 87 | class ThreadState < ASMREPL::ThreadState.build(fields) 88 | private 89 | 90 | def read_flags; eflags; end 91 | 92 | def other_registers 93 | super - ["orig_rax"] 94 | end 95 | end 96 | 97 | class Tracer 98 | def initialize pid 99 | @pid = pid 100 | end 101 | 102 | def wait 103 | Process.waitpid @pid 104 | end 105 | 106 | def state 107 | state = ThreadState.malloc 108 | raise unless Linux.ptrace(PTRACE_GETREGS, @pid, 0, state).zero? 109 | 110 | state 111 | end 112 | 113 | def state= state 114 | raise unless Linux.ptrace(PTRACE_SETREGS, @pid, 0, state).zero? 115 | 116 | state 117 | end 118 | 119 | def continue 120 | unless Linux.ptrace(Linux::PTRACE_CONT, @pid, 1, 0).zero? 121 | raise 122 | end 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/asmrepl/macos.rb: -------------------------------------------------------------------------------- 1 | require "fisk/helpers" 2 | require "asmrepl/thread_state" 3 | 4 | module ASMREPL 5 | module MacOS 6 | include Fiddle 7 | 8 | def self.make_function name, args, ret 9 | ptr = Handle::DEFAULT[name] 10 | func = Function.new ptr, args, ret, name: name 11 | define_singleton_method name, &func.to_proc 12 | end 13 | 14 | # from sys/mman.h on macOS 15 | PROT_READ = 0x01 16 | PROT_WRITE = 0x02 17 | PROT_EXEC = 0x04 18 | MAP_PRIVATE = 0x0002 19 | MAP_SHARED = 0x0001 20 | MAP_ANON = 0x1000 21 | 22 | make_function "ptrace", [TYPE_INT, TYPE_INT, TYPE_VOIDP, TYPE_INT], TYPE_INT 23 | make_function "memset", [TYPE_VOIDP, TYPE_INT, TYPE_SIZE_T], TYPE_VOID 24 | make_function "strerror", [TYPE_INT], TYPE_CONST_STRING 25 | make_function "mach_task_self", [], TYPE_VOIDP 26 | make_function "task_threads", [TYPE_VOIDP, TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP 27 | make_function "task_for_pid", [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP], TYPE_INT 28 | make_function "task_threads", [TYPE_VOIDP, TYPE_VOIDP, TYPE_VOIDP], TYPE_INT 29 | make_function "thread_get_state", [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP, TYPE_VOIDP], TYPE_INT 30 | make_function "thread_set_state", [TYPE_VOIDP, TYPE_INT, TYPE_VOIDP, TYPE_INT], TYPE_INT 31 | make_function "mmap", [TYPE_VOIDP, 32 | TYPE_SIZE_T, 33 | TYPE_INT, 34 | TYPE_INT, 35 | TYPE_INT, 36 | TYPE_INT], TYPE_VOIDP 37 | 38 | def self.mmap_jit size 39 | ptr = mmap 0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, -1, 0 40 | ptr.size = size 41 | ptr 42 | end 43 | 44 | def self.jitbuffer size 45 | Fisk::Helpers::JITBuffer.new mmap_jit(size), size 46 | end 47 | 48 | def self.traceme 49 | raise unless ptrace(PT_TRACE_ME, 0, 0, 0).zero? 50 | end 51 | 52 | fields = (<<-eostruct).scan(/uint64_t ([^;]*);/).flatten 53 | struct x86_thread_state64_t { 54 | uint64_t rax; 55 | uint64_t rbx; 56 | uint64_t rcx; 57 | uint64_t rdx; 58 | uint64_t rdi; 59 | uint64_t rsi; 60 | uint64_t rbp; 61 | uint64_t rsp; 62 | uint64_t r8; 63 | uint64_t r9; 64 | uint64_t r10; 65 | uint64_t r11; 66 | uint64_t r12; 67 | uint64_t r13; 68 | uint64_t r14; 69 | uint64_t r15; 70 | uint64_t rip; 71 | uint64_t rflags; 72 | uint64_t cs; 73 | uint64_t fs; 74 | uint64_t gs; 75 | } 76 | eostruct 77 | 78 | class ThreadState < ASMREPL::ThreadState.build(fields) 79 | private 80 | 81 | def read_flags; rflags; end 82 | end 83 | 84 | PT_TRACE_ME = 0 85 | PT_CONTINUE = 7 86 | 87 | class Tracer 88 | def initialize pid 89 | @pid = pid 90 | @target = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP) 91 | 92 | unless MacOS.task_for_pid(MacOS.mach_task_self, pid, @target.ref).zero? 93 | raise "Couldn't get task pid. Did you run with sudo?" 94 | end 95 | 96 | @thread_list = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP) 97 | thread_count = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP) 98 | 99 | raise unless MacOS.task_threads(@target, @thread_list.ref, thread_count).zero? 100 | 101 | @thread = Fiddle::Pointer.new(@thread_list[0, Fiddle::SIZEOF_VOIDP].unpack1("l!")) 102 | end 103 | 104 | def wait 105 | Process.waitpid @pid 106 | end 107 | 108 | def state 109 | 3.times do 110 | # Probably should use this for something 111 | # count = thread_count[0] 112 | 113 | # I can't remember what header I found this in, but it's from a macOS header 114 | # :sweat-smile: 115 | x86_THREAD_STATE64_COUNT = ThreadState.sizeof / Fiddle::SIZEOF_INT 116 | 117 | # Same here 118 | x86_THREAD_STATE64 = 4 119 | 120 | state_count = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT64_T) 121 | state_count[0, Fiddle::SIZEOF_INT64_T] = [x86_THREAD_STATE64_COUNT].pack("l!") 122 | 123 | state = ThreadState.malloc 124 | if MacOS.thread_get_state(@thread, x86_THREAD_STATE64, state, state_count).zero? 125 | return state 126 | end 127 | end 128 | 129 | raise "Couldn't get CPU state" 130 | end 131 | 132 | def state= new_state 133 | # I can't remember what header I found this in, but it's from a macOS header 134 | # :sweat-smile: 135 | x86_THREAD_STATE64_COUNT = ThreadState.sizeof / Fiddle::SIZEOF_INT 136 | 137 | # Same here 138 | x86_THREAD_STATE64 = 4 139 | 140 | raise unless MacOS.thread_set_state(@thread, x86_THREAD_STATE64, new_state, x86_THREAD_STATE64_COUNT).zero? 141 | end 142 | 143 | def continue 144 | unless MacOS.ptrace(MacOS::PT_CONTINUE, @pid, 1, 0).zero? 145 | raise 146 | end 147 | end 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/asmrepl/parser.rb: -------------------------------------------------------------------------------- 1 | require "racc/parser" 2 | require "ripper" 3 | require "fisk" 4 | require "set" 5 | require "strscan" 6 | 7 | module ASMREPL 8 | class Lexer 9 | INTEGER = /(?:[-+]?0b[0-1_,]+ (?# base 2) 10 | |[-+]?(?:0(?!x)|[1-9][0-9_,]*) (?# base 10) 11 | |[-+]?0x[0-9a-fA-F_,]+ (?# base 16))/x 12 | 13 | def initialize input 14 | @input = input 15 | @scanner = StringScanner.new input 16 | end 17 | 18 | REGISTERS = Set.new Fisk::Registers.constants.map(&:to_s) 19 | INSTRUCTIONS = Set.new Fisk::Instructions.constants.map(&:to_s) 20 | 21 | def next_token 22 | return if @scanner.eos? 23 | 24 | if @scanner.scan(INTEGER) 25 | [:on_int, @scanner.matched] 26 | elsif @scanner.scan(/\[/) 27 | [:on_lbracket, @scanner.matched] 28 | elsif @scanner.scan(/\]/) 29 | [:on_rbracket, @scanner.matched] 30 | elsif @scanner.scan(/,/) 31 | [:on_comma, @scanner.matched] 32 | elsif @scanner.scan(/qword/) 33 | [:qword, @scanner.matched] 34 | elsif @scanner.scan(/dword/) 35 | [:dword, @scanner.matched] 36 | elsif @scanner.scan(/word/) 37 | [:word, @scanner.matched] 38 | elsif @scanner.scan(/byte/) 39 | [:byte, @scanner.matched] 40 | elsif @scanner.scan(/ptr/) 41 | [:ptr, @scanner.matched] 42 | elsif @scanner.scan(/rip/i) 43 | [:on_rip, @scanner.matched] 44 | elsif @scanner.scan(/int/i) 45 | [:on_instruction, Fisk::Instructions::INT] 46 | elsif @scanner.scan(/movabs/i) 47 | [:on_instruction, Fisk::Instructions::MOV] 48 | elsif @scanner.scan(/\w+/) 49 | ident = @scanner.matched 50 | if INSTRUCTIONS.include?(ident.upcase) 51 | [:on_instruction, Fisk::Instructions.const_get(ident.upcase)] 52 | elsif REGISTERS.include?(ident.upcase) 53 | [:on_register, Fisk::Registers.const_get(ident.upcase)] 54 | else 55 | [:on_ident, @scanner.matched] 56 | end 57 | elsif @scanner.scan(/\s+/) 58 | [:on_sp, @scanner.matched] 59 | elsif @scanner.scan(/\+/) 60 | [:plus, @scanner.matched] 61 | elsif @scanner.scan(/-/) 62 | [:minus, @scanner.matched] 63 | else 64 | raise 65 | end 66 | end 67 | end 68 | 69 | class Parser < Racc::Parser 70 | def initialize 71 | @registers = Set.new Fisk::Registers.constants.map(&:to_s) 72 | @instructions = Set.new Fisk::Instructions.constants.map(&:to_s) 73 | end 74 | 75 | def parse input 76 | @lexer = Lexer.new input 77 | do_parse 78 | end 79 | 80 | def new_command mnemonic, arg1, arg2 81 | [:command, mnemonic, arg1, arg2] 82 | end 83 | 84 | def new_tuple mnemonic, arg1 85 | [:command, mnemonic, arg1] 86 | end 87 | 88 | def new_single mnemonic 89 | [:command, mnemonic] 90 | end 91 | 92 | def next_token 93 | while tok = @lexer.next_token 94 | next if tok.first == :on_sp 95 | return tok 96 | end 97 | end 98 | end 99 | end 100 | 101 | require "asmrepl/parser.tab" 102 | -------------------------------------------------------------------------------- /lib/asmrepl/parser.tab.rb: -------------------------------------------------------------------------------- 1 | # 2 | # DO NOT MODIFY!!!! 3 | # This file is automatically generated by Racc 1.5.1 4 | # from Racc grammar file "". 5 | # 6 | 7 | require 'racc/parser.rb' 8 | module ASMREPL 9 | class Parser < Racc::Parser 10 | ##### State transition tables begin ### 11 | 12 | racc_action_table = [ 13 | 12, 3, 9, 4, 14, 18, 16, 15, 17, 10, 14 | 11, 12, 8, 9, 19, 14, 20, 16, 15, 17, 15 | 10, 11, 9, 8, 21, 35, 9, 37, 9, 10, 16 | 11, 22, 8, 10, 11, 10, 11, 43, 9, 45, 17 | 9, 8, 24, 8, 39, 10, 11, 10, 11, 25, 18 | 26, 27, 28, 29, 41, 42, 47, 48 ] 19 | 20 | racc_action_check = [ 21 | 2, 0, 2, 1, 2, 4, 2, 2, 2, 2, 22 | 2, 19, 2, 19, 5, 19, 6, 19, 19, 19, 23 | 19, 19, 20, 19, 10, 23, 23, 24, 24, 20, 24 | 20, 11, 20, 23, 23, 24, 24, 39, 39, 40, 25 | 40, 12, 12, 25, 25, 39, 39, 40, 40, 13, 26 | 14, 15, 16, 17, 36, 38, 44, 46 ] 27 | 28 | racc_action_pointer = [ 29 | -12, 3, -2, nil, 5, 9, 11, nil, nil, nil, 30 | 20, 27, 27, 47, 43, 44, 45, 46, nil, 9, 31 | 18, nil, nil, 22, 24, 29, nil, nil, nil, nil, 32 | nil, nil, nil, nil, nil, nil, 51, nil, 52, 34, 33 | 36, nil, nil, nil, 53, nil, 54, nil, nil ] 34 | 35 | racc_action_default = [ 36 | -27, -27, -9, -10, -27, -6, -7, -8, -11, -12, 37 | -27, -27, -27, -27, -27, -27, -27, -27, 49, -27, 38 | -27, -13, -14, -27, -27, -27, -23, -24, -25, -26, 39 | -1, -2, -3, -4, -5, -15, -27, -16, -27, -27, 40 | -27, -20, -17, -18, -27, -21, -27, -19, -22 ] 41 | 42 | racc_goto_table = [ 43 | 7, 1, 5, 2, nil, nil, 6, nil, nil, nil, 44 | nil, nil, 23, nil, nil, nil, nil, 30, 34, 31, 45 | 33, 36, 38, 32, nil, 40, nil, nil, nil, nil, 46 | nil, nil, nil, nil, nil, nil, nil, 44, 46 ] 47 | 48 | racc_goto_check = [ 49 | 4, 1, 3, 2, nil, nil, 5, nil, nil, nil, 50 | nil, nil, 3, nil, nil, nil, nil, 4, 4, 3, 51 | 3, 4, 4, 5, nil, 3, nil, nil, nil, nil, 52 | nil, nil, nil, nil, nil, nil, nil, 4, 4 ] 53 | 54 | racc_goto_pointer = [ 55 | nil, 1, 3, 0, -2, 4, nil ] 56 | 57 | racc_goto_default = [ 58 | nil, nil, nil, nil, nil, nil, 13 ] 59 | 60 | racc_reduce_table = [ 61 | 0, 0, :racc_error, 62 | 4, 17, :_reduce_1, 63 | 4, 17, :_reduce_2, 64 | 4, 17, :_reduce_3, 65 | 4, 17, :_reduce_4, 66 | 4, 17, :_reduce_5, 67 | 2, 17, :_reduce_6, 68 | 2, 17, :_reduce_7, 69 | 2, 17, :_reduce_8, 70 | 1, 17, :_reduce_9, 71 | 1, 18, :_reduce_10, 72 | 1, 19, :_reduce_11, 73 | 1, 20, :_reduce_12, 74 | 2, 20, :_reduce_13, 75 | 2, 20, :_reduce_14, 76 | 3, 21, :_reduce_15, 77 | 3, 21, :_reduce_16, 78 | 4, 21, :_reduce_17, 79 | 4, 21, :_reduce_18, 80 | 5, 21, :_reduce_19, 81 | 4, 21, :_reduce_20, 82 | 4, 21, :_reduce_21, 83 | 5, 21, :_reduce_22, 84 | 2, 22, :_reduce_23, 85 | 2, 22, :_reduce_24, 86 | 2, 22, :_reduce_25, 87 | 2, 22, :_reduce_26 ] 88 | 89 | racc_reduce_n = 27 90 | 91 | racc_shift_n = 49 92 | 93 | racc_token_table = { 94 | false => 0, 95 | :error => 1, 96 | :on_lbracket => 2, 97 | :on_rbracket => 3, 98 | :on_int => 4, 99 | :on_comma => 5, 100 | :qword => 6, 101 | :ptr => 7, 102 | :word => 8, 103 | :dword => 9, 104 | :byte => 10, 105 | :plus => 11, 106 | :minus => 12, 107 | :on_instruction => 13, 108 | :on_register => 14, 109 | :on_rip => 15 } 110 | 111 | racc_nt_base = 16 112 | 113 | racc_use_result_var = true 114 | 115 | Racc_arg = [ 116 | racc_action_table, 117 | racc_action_check, 118 | racc_action_default, 119 | racc_action_pointer, 120 | racc_goto_table, 121 | racc_goto_check, 122 | racc_goto_default, 123 | racc_goto_pointer, 124 | racc_nt_base, 125 | racc_reduce_table, 126 | racc_token_table, 127 | racc_shift_n, 128 | racc_reduce_n, 129 | racc_use_result_var ] 130 | 131 | Racc_token_to_s_table = [ 132 | "$end", 133 | "error", 134 | "on_lbracket", 135 | "on_rbracket", 136 | "on_int", 137 | "on_comma", 138 | "qword", 139 | "ptr", 140 | "word", 141 | "dword", 142 | "byte", 143 | "plus", 144 | "minus", 145 | "on_instruction", 146 | "on_register", 147 | "on_rip", 148 | "$start", 149 | "command", 150 | "instruction", 151 | "register", 152 | "int", 153 | "memory", 154 | "memsize" ] 155 | 156 | Racc_debug_parser = false 157 | 158 | ##### State transition tables end ##### 159 | 160 | # reduce 0 omitted 161 | 162 | def _reduce_1(val, _values, result) 163 | result = new_command(val[0], val[1], val[3]) 164 | result 165 | end 166 | 167 | def _reduce_2(val, _values, result) 168 | result = new_command(val[0], val[1], val[3]) 169 | result 170 | end 171 | 172 | def _reduce_3(val, _values, result) 173 | result = new_command(val[0], val[1], val[3]) 174 | result 175 | end 176 | 177 | def _reduce_4(val, _values, result) 178 | result = new_command(val[0], val[1], val[3]) 179 | result 180 | end 181 | 182 | def _reduce_5(val, _values, result) 183 | result = new_command(val[0], val[1], val[3]) 184 | result 185 | end 186 | 187 | def _reduce_6(val, _values, result) 188 | result = new_tuple(val[0], val[1]) 189 | result 190 | end 191 | 192 | def _reduce_7(val, _values, result) 193 | result = new_tuple(val[0], val[1]) 194 | result 195 | end 196 | 197 | def _reduce_8(val, _values, result) 198 | result = new_tuple(val[0], val[1]) 199 | result 200 | end 201 | 202 | def _reduce_9(val, _values, result) 203 | result = new_single(val[0]) 204 | result 205 | end 206 | 207 | def _reduce_10(val, _values, result) 208 | result = [:instruction, val[0]] 209 | result 210 | end 211 | 212 | def _reduce_11(val, _values, result) 213 | result = [:register, val[0]] 214 | result 215 | end 216 | 217 | def _reduce_12(val, _values, result) 218 | result = [:int, Integer(val[0])] 219 | result 220 | end 221 | 222 | def _reduce_13(val, _values, result) 223 | result = [:int, Integer(val[1])] 224 | result 225 | end 226 | 227 | def _reduce_14(val, _values, result) 228 | result = [:int, -Integer(val[1])] 229 | result 230 | end 231 | 232 | def _reduce_15(val, _values, result) 233 | result = [:memory, Fisk::M64.new(val[1].last, 0)] 234 | result 235 | end 236 | 237 | def _reduce_16(val, _values, result) 238 | result = [:memory, Fisk::Registers::Rip.new(0)] 239 | result 240 | end 241 | 242 | def _reduce_17(val, _values, result) 243 | result = [:memory, Fisk::Registers::Rip.new(val[2].last)] 244 | result 245 | end 246 | 247 | def _reduce_18(val, _values, result) 248 | result = [:memory, Fisk::Registers::Rip.new(0)] 249 | result 250 | end 251 | 252 | def _reduce_19(val, _values, result) 253 | result = [:memory, Fisk::Registers::Rip.new(val[3].last)] 254 | result 255 | end 256 | 257 | def _reduce_20(val, _values, result) 258 | result = [:memory, Fisk::M64.new(val[1].last, val[2].last)] 259 | result 260 | end 261 | 262 | def _reduce_21(val, _values, result) 263 | result = [:memory, val[0].new(val[2].last, 0)] 264 | result 265 | end 266 | 267 | def _reduce_22(val, _values, result) 268 | result = [:memory, val[0].new(val[2].last, val[3].last)] 269 | result 270 | end 271 | 272 | def _reduce_23(val, _values, result) 273 | result = Fisk::M64 274 | result 275 | end 276 | 277 | def _reduce_24(val, _values, result) 278 | result = Fisk::M32 279 | result 280 | end 281 | 282 | def _reduce_25(val, _values, result) 283 | result = Fisk::M16 284 | result 285 | end 286 | 287 | def _reduce_26(val, _values, result) 288 | result = Fisk::M8 289 | result 290 | end 291 | 292 | def _reduce_none(val, _values, result) 293 | val[0] 294 | end 295 | 296 | end # class Parser 297 | end # module ASMREPL 298 | -------------------------------------------------------------------------------- /lib/asmrepl/parser.y: -------------------------------------------------------------------------------- 1 | class ASMREPL::Parser 2 | token on_lbracket on_rbracket on_int on_comma qword ptr word dword byte 3 | token plus minus on_instruction on_register on_rip 4 | 5 | rule 6 | 7 | command: instruction register on_comma int { result = new_command(val[0], val[1], val[3]) } 8 | | instruction register on_comma register { result = new_command(val[0], val[1], val[3]) } 9 | | instruction register on_comma memory { result = new_command(val[0], val[1], val[3]) } 10 | | instruction memory on_comma register { result = new_command(val[0], val[1], val[3]) } 11 | | instruction memory on_comma int { result = new_command(val[0], val[1], val[3]) } 12 | | instruction register { result = new_tuple(val[0], val[1]) } 13 | | instruction memory { result = new_tuple(val[0], val[1]) } 14 | | instruction int { result = new_tuple(val[0], val[1]) } 15 | | instruction { result = new_single(val[0]) } 16 | ; 17 | 18 | instruction: on_instruction { result = [:instruction, val[0]] } 19 | ; 20 | 21 | register: on_register { result = [:register, val[0]] } 22 | ; 23 | 24 | int: on_int { result = [:int, Integer(val[0])] } 25 | | plus on_int { result = [:int, Integer(val[1])] } 26 | | minus on_int { result = [:int, -Integer(val[1])] } 27 | ; 28 | 29 | memory: on_lbracket register on_rbracket { result = [:memory, Fisk::M64.new(val[1].last, 0)] } 30 | | on_lbracket on_rip on_rbracket { result = [:memory, Fisk::Registers::Rip.new(0)] } 31 | | on_lbracket on_rip int on_rbracket { result = [:memory, Fisk::Registers::Rip.new(val[2].last)] } 32 | | memsize on_lbracket on_rip on_rbracket { result = [:memory, Fisk::Registers::Rip.new(0)] } 33 | | memsize on_lbracket on_rip int on_rbracket { result = [:memory, Fisk::Registers::Rip.new(val[3].last)] } 34 | | on_lbracket register int on_rbracket { result = [:memory, Fisk::M64.new(val[1].last, val[2].last)] } 35 | | memsize on_lbracket register on_rbracket { result = [:memory, val[0].new(val[2].last, 0)] } 36 | | memsize on_lbracket register int on_rbracket { result = [:memory, val[0].new(val[2].last, val[3].last)] } 37 | ; 38 | 39 | memsize: qword ptr { result = Fisk::M64 } 40 | | dword ptr { result = Fisk::M32 } 41 | | word ptr { result = Fisk::M16 } 42 | | byte ptr { result = Fisk::M8 } 43 | ; 44 | end 45 | -------------------------------------------------------------------------------- /lib/asmrepl/repl.rb: -------------------------------------------------------------------------------- 1 | require "fiddle" 2 | require "fisk/helpers" 3 | require "reline" 4 | 5 | if RUBY_PLATFORM =~ /darwin/ 6 | require "asmrepl/macos" 7 | else 8 | require "asmrepl/linux" 9 | end 10 | 11 | module ASMREPL 12 | MAXINT = 0xFFFFFFFFFFFFFFFF 13 | 14 | class REPL 15 | include Fiddle 16 | 17 | if RUBY_PLATFORM =~ /darwin/ 18 | CFuncs = MacOS 19 | else 20 | CFuncs = Linux 21 | end 22 | 23 | def initialize 24 | size = 1024 * 16 # 16k is enough for anyone! 25 | @buffer = CFuncs.jitbuffer(size) 26 | CFuncs.memset(@buffer.memory, 0xCC, size) 27 | @parser = ASMREPL::Parser.new 28 | @assembler = ASMREPL::Assembler.new 29 | end 30 | 31 | def display_state state 32 | puts bold(" CPU STATE ".center(48, "=")) 33 | puts state 34 | puts 35 | puts "FLAGS: #{state.flags.inspect}" 36 | puts 37 | end 38 | 39 | def display_state_change last_state, state 40 | puts bold(" REGISTER CHANGES ".center(48, "=")) 41 | show_flags = false 42 | 43 | state.fields.each do |field| 44 | next if field == "rip" 45 | 46 | if last_state[field] != state[field] 47 | print "#{field.ljust(6)} " 48 | print sprintf("%#018x", last_state[field] & MAXINT) 49 | print " => " 50 | puts bold(sprintf("%#018x", state[field] & MAXINT)) 51 | end 52 | end 53 | 54 | if last_state.flags != state.flags 55 | puts 56 | puts "FLAGS: #{state.flags.inspect}" 57 | end 58 | 59 | puts 60 | end 61 | 62 | def bold string 63 | "\e[1m#{string}\e[0m" 64 | end 65 | 66 | def start 67 | pid = fork { 68 | CFuncs.traceme 69 | @buffer.to_function([], TYPE_INT).call 70 | } 71 | 72 | tracer = CFuncs::Tracer.new pid 73 | should_cpu = true 74 | last_state = nil 75 | 76 | while tracer.wait 77 | state = tracer.state 78 | 79 | # Show CPU state once on boot 80 | if last_state.nil? 81 | display_state state 82 | last_state = state 83 | else 84 | display_state_change last_state, state 85 | last_state = state 86 | end 87 | 88 | use_history = true 89 | begin 90 | loop do 91 | cmd = nil 92 | prompt = sprintf("(rip %#018x)> ", state.rip) 93 | text = Reline.readmultiline(prompt, use_history) do |multiline_input| 94 | case multiline_input 95 | when /\Adisasm\Z/ 96 | cmd = :disasm 97 | when /\A\s*(\w+)\s*\Z/ 98 | register = $1 99 | cmd = [:read, register] 100 | when /\A\s*(\w+)\s*=\s*(\d+)\Z/ 101 | register = $1 102 | cmd = [:write, register, $2.to_i] 103 | else 104 | cmd = :run 105 | end 106 | true 107 | end 108 | 109 | case cmd 110 | in :disasm 111 | # disassembles the JIT buffer. This is just for development, 112 | # I don't want to make a hard dependency on crabstone right now. 113 | # If you want to use this, install crabstone 114 | begin 115 | require "asmrepl/disasm" 116 | ASMREPL::Disasm.disasm @buffer 117 | rescue 118 | end 119 | in :run 120 | break if text.chomp.empty? 121 | begin 122 | parser_result = @parser.parse text.chomp 123 | rescue 124 | puts "Invalid instruction" 125 | next 126 | end 127 | 128 | begin 129 | binary = @assembler.assemble parser_result 130 | 131 | # Move the JIT buffer to the current instruction pointer, but 132 | # rewind RIP so that we write over the int3 133 | pos = (state.rip - @buffer.memory.to_i - 1) 134 | @buffer.seek pos 135 | binary.bytes.each { |byte| @buffer.putc byte } 136 | state.rip -= 1 137 | tracer.state = state 138 | rescue Fisk::Errors::InvalidInstructionError => e 139 | # Print an error message when the instruction is invalid 140 | puts e.message 141 | next 142 | end 143 | break 144 | in [:write, reg, val] 145 | state[reg] = val 146 | tracer.state = state 147 | in [:read, "cpu"] 148 | display_state state 149 | in [:read, reg] 150 | val = state[reg] 151 | if val 152 | puts sprintf("%#018x", state[reg]) 153 | else 154 | puts "Unknown command: " 155 | puts " " + text 156 | end 157 | else 158 | end 159 | end 160 | rescue Interrupt 161 | puts "" 162 | exit 0 163 | end 164 | tracer.continue 165 | end 166 | end 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /lib/asmrepl/thread_state.rb: -------------------------------------------------------------------------------- 1 | require "fiddle" 2 | 3 | module ASMREPL 4 | class ThreadState 5 | def self.sizeof 6 | fields.length * Fiddle::SIZEOF_INT64_T 7 | end 8 | 9 | def self.malloc 10 | new Fiddle::Pointer.malloc sizeof 11 | end 12 | 13 | FLAGS = [ 14 | ['CF', 'Carry Flag'], 15 | [nil, 'Reserved'], 16 | ['PF', 'Parity Flag'], 17 | [nil, 'Reserved'], 18 | ['AF', 'Adjust Flag'], 19 | [nil, 'Reserved'], 20 | ['ZF', 'Zero Flag'], 21 | ['SF', 'Sign Flag'], 22 | ['TF', 'Trap Flag'], 23 | ['IF', 'Interrupt Enable Flag'], 24 | ['DF', 'Direction Flag'], 25 | ['OF', 'Overflow Flag'], 26 | ['IOPL_H', 'I/O privilege level High bit'], 27 | ['IOPL_L', 'I/O privilege level Low bit'], 28 | ['NT', 'Nested Task Flag'], 29 | [nil, 'Reserved'], 30 | ] 31 | 32 | attr_reader :to_ptr 33 | 34 | def initialize buffer 35 | @to_ptr = buffer 36 | end 37 | 38 | def [] name 39 | idx = fields.index(name) 40 | return unless idx 41 | to_ptr[Fiddle::SIZEOF_INT64_T * idx, Fiddle::SIZEOF_INT64_T].unpack1("l!") 42 | end 43 | 44 | def []= name, val 45 | idx = fields.index(name) 46 | return unless idx 47 | to_ptr[Fiddle::SIZEOF_INT64_T * idx, Fiddle::SIZEOF_INT64_T] = [val].pack("l!") 48 | end 49 | 50 | def flags 51 | flags = read_flags 52 | f = [] 53 | FLAGS.each do |abbrv, _| 54 | if abbrv && flags & 1 == 1 55 | f << abbrv 56 | end 57 | flags >>= 1 58 | end 59 | f 60 | end 61 | 62 | def to_s 63 | buf = "" 64 | display_registers.first(8).zip(display_registers.drop(8)).each do |l, r| 65 | buf << "#{l.ljust(3)} #{sprintf("%#018x", self[l] & MAXINT)}" 66 | buf << " " 67 | buf << "#{r.ljust(3)} #{sprintf("%#018x", self[r] & MAXINT)}\n" 68 | end 69 | 70 | buf << "\n" 71 | 72 | other_registers.each do |reg| 73 | buf << "#{reg.ljust(7)} #{sprintf("%#018x", self[reg] & MAXINT)}\n" 74 | end 75 | buf 76 | end 77 | 78 | def display_registers 79 | %w{ rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 } 80 | end 81 | 82 | def other_registers 83 | fields - display_registers 84 | end 85 | 86 | def self.build fields 87 | Class.new(ThreadState) do 88 | define_method(:fields) do 89 | fields 90 | end 91 | 92 | define_singleton_method(:fields) do 93 | fields 94 | end 95 | 96 | fields.each_with_index do |field, i| 97 | define_method(field) do 98 | to_ptr[Fiddle::SIZEOF_INT64_T * i, Fiddle::SIZEOF_INT64_T].unpack1("l!") 99 | end 100 | 101 | define_method("#{field}=") do |v| 102 | to_ptr[Fiddle::SIZEOF_INT64_T * i, Fiddle::SIZEOF_INT64_T] = [v].pack("l!") 103 | end 104 | end 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/asmrepl/version.rb: -------------------------------------------------------------------------------- 1 | module ASMREPL 2 | VERSION = '1.1.0' 3 | end 4 | -------------------------------------------------------------------------------- /test/asmrepl_test.rb: -------------------------------------------------------------------------------- 1 | require "helper" 2 | 3 | class ParserTest < ASMREPL::Test 4 | def setup 5 | super 6 | @parser = ASMREPL::Parser.new 7 | @assembler = ASMREPL::Assembler.new 8 | end 9 | 10 | def test_int3 11 | assert_round_trip "int3 " 12 | end 13 | 14 | def test_and 15 | assert_round_trip "and r9, 0xffff" 16 | end 17 | 18 | def test_negative 19 | assert_round_trip "test qword ptr [r15], -9" 20 | end 21 | 22 | def test_mem_lhs 23 | asm = disasm_for "mov [r15], r9" 24 | assert_equal "mov qword ptr [r15], r9", asm 25 | assert_round_trip "mov qword ptr [r15], r9" 26 | end 27 | 28 | def test_inc 29 | assert_round_trip "inc r10" 30 | assert_round_trip "inc qword ptr [r10]" 31 | assert_round_trip "inc qword ptr [r10 + 8]" 32 | end 33 | 34 | def test_movabs 35 | assert_round_trip "movabs r10, 0x6000014b1ae0" 36 | end 37 | 38 | def test_ret 39 | assert_round_trip "ret " 40 | end 41 | 42 | def test_lea_rip 43 | if RUBY_PLATFORM =~ /darwin/ 44 | assert_round_trip "lea rax, [rip]" 45 | assert_round_trip "lea rax, [rip + 9]" 46 | else 47 | assert_round_trip "lea rax, qword ptr [rip]" 48 | assert_round_trip "lea rax, qword ptr [rip + 9]" 49 | end 50 | end 51 | 52 | def test_push_reg 53 | assert_round_trip "push r13" 54 | end 55 | 56 | def test_int_4 57 | assert_round_trip "int 4" 58 | end 59 | 60 | def test_shl_1 61 | assert_round_trip "shl rax, 1" 62 | end 63 | 64 | def test_simple 65 | assert_round_trip "mov rax, 0xff" 66 | assert_round_trip "mov r8, 0xff" 67 | end 68 | 69 | def test_2_regs 70 | assert_round_trip "mov r8, rax" 71 | end 72 | 73 | def test_memory_offset 74 | assert_round_trip "mov r8, qword ptr [rax + 0x7b]" 75 | assert_round_trip "mov r8, qword ptr [rax - 0x7b]" 76 | end 77 | 78 | def test_memory_defaults_64_offset 79 | asm = disasm_for "mov rax, [rax + 0x7b]" 80 | assert_equal "mov rax, qword ptr [rax + 0x7b]", asm 81 | 82 | asm = disasm_for "mov rax, [rax - 0x7b]" 83 | assert_equal "mov rax, qword ptr [rax - 0x7b]", asm 84 | end 85 | 86 | def test_memory_defaults_64 87 | asm = disasm_for "mov rax, [rax]" 88 | assert_equal "mov rax, qword ptr [rax]", asm 89 | end 90 | 91 | def test_memory_specify_width 92 | assert_round_trip "mov r8, qword ptr [rax]" 93 | assert_round_trip "mov r8d, dword ptr [r8]" 94 | assert_round_trip "mov r8w, word ptr [r8]" 95 | assert_round_trip "mov r8b, byte ptr [r8]" 96 | end 97 | 98 | def assert_round_trip asm 99 | ast = @parser.parse asm 100 | binary = @assembler.assemble ast 101 | assert_equal asm, disasm(binary) 102 | end 103 | 104 | def disasm_for asm 105 | ast = @parser.parse asm 106 | binary = @assembler.assemble ast 107 | disasm(binary) 108 | end 109 | 110 | def disasm binary 111 | cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) 112 | cs.disasm(binary, 0x0000).each {|i| 113 | return "%s %s" % [i.mnemonic, i.op_str] 114 | } 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require "asmrepl" 2 | require "minitest" 3 | require "minitest/autorun" 4 | require "crabstone" 5 | 6 | class Crabstone::Binding::Instruction 7 | class << self 8 | alias :old_release :release 9 | end 10 | 11 | # Squelch error in crabstone 12 | def self.release obj 13 | nil 14 | end 15 | end 16 | 17 | module ASMREPL 18 | class Test < Minitest::Test 19 | end 20 | end 21 | --------------------------------------------------------------------------------