From 32c13df6f51e2ba2581bd08bc5373bc5df823801 Mon Sep 17 00:00:00 2001
From: Ingmar Steen <iksteen@gmail.com>
Date: Tue, 10 May 2016 17:58:05 +0200
Subject: [PATCH] Add nodejs bindings

---
 bindings/const_generator.py        |  18 ++++++
 bindings/nodejs/LICENSE            |  19 ++++++
 bindings/nodejs/README.md          |  53 +++++++++++++++
 bindings/nodejs/consts/arm.js      |   4 ++
 bindings/nodejs/consts/arm64.js    |   4 ++
 bindings/nodejs/consts/hexagon.js  |   4 ++
 bindings/nodejs/consts/index.js    |   9 +++
 bindings/nodejs/consts/keystone.js |  75 ++++++++++++++++++++++
 bindings/nodejs/consts/mips.js     |   4 ++
 bindings/nodejs/consts/ppc.js      |   4 ++
 bindings/nodejs/consts/sparc.js    |   4 ++
 bindings/nodejs/consts/systemz.js  |   4 ++
 bindings/nodejs/consts/x86.js      |   4 ++
 bindings/nodejs/index.js           | 100 +++++++++++++++++++++++++++++
 bindings/nodejs/package.json       |  18 ++++++
 bindings/nodejs/sample.js          |  27 ++++++++
 16 files changed, 351 insertions(+)
 create mode 100644 bindings/nodejs/LICENSE
 create mode 100644 bindings/nodejs/README.md
 create mode 100644 bindings/nodejs/consts/arm.js
 create mode 100644 bindings/nodejs/consts/arm64.js
 create mode 100644 bindings/nodejs/consts/hexagon.js
 create mode 100644 bindings/nodejs/consts/index.js
 create mode 100644 bindings/nodejs/consts/keystone.js
 create mode 100644 bindings/nodejs/consts/mips.js
 create mode 100644 bindings/nodejs/consts/ppc.js
 create mode 100644 bindings/nodejs/consts/sparc.js
 create mode 100644 bindings/nodejs/consts/systemz.js
 create mode 100644 bindings/nodejs/consts/x86.js
 create mode 100644 bindings/nodejs/index.js
 create mode 100644 bindings/nodejs/package.json
 create mode 100644 bindings/nodejs/sample.js

diff --git a/bindings/const_generator.py b/bindings/const_generator.py
index 909f835..96db7cb 100644
--- a/bindings/const_generator.py
+++ b/bindings/const_generator.py
@@ -29,6 +29,24 @@ template = {
             'comment_open': '#',
             'comment_close': '',
         },
+    'nodejs': {
+            'header': "// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [%s_const.js]\n",
+            'footer': "",
+            'line_format': 'module.exports.%s = %s\n',
+            'out_file': './nodejs/consts/%s.js',
+            # prefixes for constant filenames of all archs - case sensitive
+            'arm.h': 'arm',
+            'arm64.h': 'arm64',
+            'mips.h': 'mips',
+            'x86.h': 'x86',
+            'sparc.h': 'sparc',
+            'systemz.h': 'systemz',
+            'ppc.h': 'ppc',
+            'hexagon.h': 'hexagon',
+            'keystone.h': 'keystone',
+            'comment_open': '//',
+            'comment_close': '',
+    },
 }
 
 # markup for comments to be added to autogen files
diff --git a/bindings/nodejs/LICENSE b/bindings/nodejs/LICENSE
new file mode 100644
index 0000000..89cc025
--- /dev/null
+++ b/bindings/nodejs/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 Ingmar Steen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md
new file mode 100644
index 0000000..83ed4af
--- /dev/null
+++ b/bindings/nodejs/README.md
@@ -0,0 +1,53 @@
+# node-keystone
+
+`node-keystone` provides Node.js bindings for the
+[Keystone](http://www.keystone-engine.org) assembler library, allowing
+text data in to be assembled into `Buffer` objects using any of Keystone's
+supported architectures.
+
+### Install
+
+`npm install /path/to/keystone/bindings/nodejs`
+
+#### libkeystone
+
+These bindings require you to have the Keystone library installed as it is
+not included.
+
+### Basic usage
+
+```javascript
+var keystone = require("keystone");
+var assembly = "inc ecx; dec ebx"
+
+var ks = new keystone.Ks(keystone.ARCH_X86, keystone.MODE_64);
+console.log(ks.asm(assembly));
+ks.close();
+```
+
+For other examples, see the `example.js` file.
+
+### License
+
+The source code is hereby released under the MIT License. The full text of the
+license appears below.
+
+Copyright (c) 2016 Ingmar Steen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/bindings/nodejs/consts/arm.js b/bindings/nodejs/consts/arm.js
new file mode 100644
index 0000000..7fa6688
--- /dev/null
+++ b/bindings/nodejs/consts/arm.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [arm_const.js]
+module.exports.ERR_ASM_ARM_INVALIDOPERAND = 512
+module.exports.ERR_ASM_ARM_MISSINGFEATURE = 513
+module.exports.ERR_ASM_ARM_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/arm64.js b/bindings/nodejs/consts/arm64.js
new file mode 100644
index 0000000..4c134b6
--- /dev/null
+++ b/bindings/nodejs/consts/arm64.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [arm64_const.js]
+module.exports.ERR_ASM_ARM64_INVALIDOPERAND = 512
+module.exports.ERR_ASM_ARM64_MISSINGFEATURE = 513
+module.exports.ERR_ASM_ARM64_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/hexagon.js b/bindings/nodejs/consts/hexagon.js
new file mode 100644
index 0000000..7e94aeb
--- /dev/null
+++ b/bindings/nodejs/consts/hexagon.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [hexagon_const.js]
+module.exports.ERR_ASM_HEXAGON_INVALIDOPERAND = 512
+module.exports.ERR_ASM_HEXAGON_MISSINGFEATURE = 513
+module.exports.ERR_ASM_HEXAGON_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/index.js b/bindings/nodejs/consts/index.js
new file mode 100644
index 0000000..df58d5c
--- /dev/null
+++ b/bindings/nodejs/consts/index.js
@@ -0,0 +1,9 @@
+var extend = require('util')._extend,
+    archs = ['arm64', 'arm', 'hexagon', 'mips', 'ppc', 'sparc', 'systemz', 'x86'],
+    i
+
+module.exports = require('./keystone')
+
+for (i = 0; i < archs.length; ++i) {
+    extend(module.exports, require('./' + archs[i]));
+}
diff --git a/bindings/nodejs/consts/keystone.js b/bindings/nodejs/consts/keystone.js
new file mode 100644
index 0000000..d66b681
--- /dev/null
+++ b/bindings/nodejs/consts/keystone.js
@@ -0,0 +1,75 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [keystone_const.js]
+module.exports.API_MAJOR = 1
+
+module.exports.API_MINOR = 0
+module.exports.ARCH_ARM = 1
+module.exports.ARCH_ARM64 = 2
+module.exports.ARCH_MIPS = 3
+module.exports.ARCH_X86 = 4
+module.exports.ARCH_PPC = 5
+module.exports.ARCH_SPARC = 6
+module.exports.ARCH_SYSTEMZ = 7
+module.exports.ARCH_HEXAGON = 8
+module.exports.ARCH_MAX = 9
+
+module.exports.MODE_LITTLE_ENDIAN = 0
+module.exports.MODE_BIG_ENDIAN = 1073741824
+module.exports.MODE_ARM = 1
+module.exports.MODE_THUMB = 16
+module.exports.MODE_V8 = 64
+module.exports.MODE_MICRO = 16
+module.exports.MODE_MIPS3 = 32
+module.exports.MODE_MIPS32R6 = 64
+module.exports.MODE_MIPS32 = 4
+module.exports.MODE_MIPS64 = 8
+module.exports.MODE_16 = 2
+module.exports.MODE_32 = 4
+module.exports.MODE_64 = 8
+module.exports.MODE_PPC32 = 4
+module.exports.MODE_PPC64 = 8
+module.exports.MODE_QPX = 16
+module.exports.MODE_SPARC32 = 4
+module.exports.MODE_SPARC64 = 8
+module.exports.MODE_V9 = 16
+module.exports.ERR_ASM = 128
+module.exports.ERR_ASM_ARCH = 512
+
+module.exports.ERR_OK = 0
+module.exports.ERR_NOMEM = 1
+module.exports.ERR_ARCH = 2
+module.exports.ERR_HANDLE = 3
+module.exports.ERR_MODE = 4
+module.exports.ERR_VERSION = 5
+module.exports.ERR_OPT_INVALID = 6
+module.exports.ERR_ASM_EXPR_TOKEN = 128
+module.exports.ERR_ASM_DIRECTIVE_VALUE_RANGE = 129
+module.exports.ERR_ASM_DIRECTIVE_ID = 130
+module.exports.ERR_ASM_DIRECTIVE_TOKEN = 131
+module.exports.ERR_ASM_DIRECTIVE_STR = 132
+module.exports.ERR_ASM_DIRECTIVE_COMMA = 133
+module.exports.ERR_ASM_DIRECTIVE_RELOC_NAME = 134
+module.exports.ERR_ASM_DIRECTIVE_RELOC_TOKEN = 135
+module.exports.ERR_ASM_DIRECTIVE_FPOINT = 136
+module.exports.ERR_ASM_VARIANT_INVALID = 137
+module.exports.ERR_ASM_EXPR_BRACKET = 138
+module.exports.ERR_ASM_SYMBOL_MODIFIER = 139
+module.exports.ERR_ASM_RPAREN = 140
+module.exports.ERR_ASM_STAT_TOKEN = 141
+module.exports.ERR_ASM_UNSUPPORTED = 142
+module.exports.ERR_ASM_MACRO_TOKEN = 143
+module.exports.ERR_ASM_MACRO_PAREN = 144
+module.exports.ERR_ASM_MACRO_EQU = 145
+module.exports.ERR_ASM_MACRO_ARGS = 146
+module.exports.ERR_ASM_MACRO_LEVELS_EXCEED = 147
+module.exports.ERR_ASM_ESC_BACKSLASH = 148
+module.exports.ERR_ASM_ESC_OCTAL = 149
+module.exports.ERR_ASM_ESC_SEQUENCE = 150
+module.exports.ERR_ASM_INVALIDOPERAND = 512
+module.exports.ERR_ASM_MISSINGFEATURE = 513
+module.exports.ERR_ASM_MNEMONICFAIL = 514
+module.exports.OPT_SYNTAX = 1
+module.exports.OPT_SYNTAX_INTEL = 1
+module.exports.OPT_SYNTAX_ATT = 2
+module.exports.OPT_SYNTAX_NASM = 4
+module.exports.OPT_SYNTAX_MASM = 8
+module.exports.OPT_SYNTAX_GAS = 16
diff --git a/bindings/nodejs/consts/mips.js b/bindings/nodejs/consts/mips.js
new file mode 100644
index 0000000..076f68b
--- /dev/null
+++ b/bindings/nodejs/consts/mips.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [mips_const.js]
+module.exports.ERR_ASM_MIPS_INVALIDOPERAND = 512
+module.exports.ERR_ASM_MIPS_MISSINGFEATURE = 513
+module.exports.ERR_ASM_MIPS_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/ppc.js b/bindings/nodejs/consts/ppc.js
new file mode 100644
index 0000000..63562f4
--- /dev/null
+++ b/bindings/nodejs/consts/ppc.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [ppc_const.js]
+module.exports.ERR_ASM_PPC_INVALIDOPERAND = 512
+module.exports.ERR_ASM_PPC_MISSINGFEATURE = 513
+module.exports.ERR_ASM_PPC_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/sparc.js b/bindings/nodejs/consts/sparc.js
new file mode 100644
index 0000000..9287ce4
--- /dev/null
+++ b/bindings/nodejs/consts/sparc.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [sparc_const.js]
+module.exports.ERR_ASM_SPARC_INVALIDOPERAND = 512
+module.exports.ERR_ASM_SPARC_MISSINGFEATURE = 513
+module.exports.ERR_ASM_SPARC_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/systemz.js b/bindings/nodejs/consts/systemz.js
new file mode 100644
index 0000000..4f9a611
--- /dev/null
+++ b/bindings/nodejs/consts/systemz.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [systemz_const.js]
+module.exports.ERR_ASM_SYSTEMZ_INVALIDOPERAND = 512
+module.exports.ERR_ASM_SYSTEMZ_MISSINGFEATURE = 513
+module.exports.ERR_ASM_SYSTEMZ_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/consts/x86.js b/bindings/nodejs/consts/x86.js
new file mode 100644
index 0000000..f356afe
--- /dev/null
+++ b/bindings/nodejs/consts/x86.js
@@ -0,0 +1,4 @@
+// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [x86_const.js]
+module.exports.ERR_ASM_X86_INVALIDOPERAND = 512
+module.exports.ERR_ASM_X86_MISSINGFEATURE = 513
+module.exports.ERR_ASM_X86_MNEMONICFAIL = 514
diff --git a/bindings/nodejs/index.js b/bindings/nodejs/index.js
new file mode 100644
index 0000000..04d8e6a
--- /dev/null
+++ b/bindings/nodejs/index.js
@@ -0,0 +1,100 @@
+var ref = require('ref'),
+    ffi = require('ffi'),
+    consts = require('./consts'),
+    extend = require('util')._extend
+
+var ks_engine = 'void',
+    ks_enginePtr = ref.refType(ks_engine),
+    ks_enginePtrPtr = ref.refType(ks_enginePtr),
+    ks_arch = 'int',
+    ks_err = 'int',
+    ks_opt_type = 'int',
+    uintPtr = ref.refType('uint'),
+    ucharPtr = ref.refType('uchar'),
+    ucharPtrPtr = ref.refType(ucharPtr),
+    size_tPtr = ref.refType('size_t'),
+    stringPtr = ref.refType('string')
+
+var Keystone = ffi.Library('libkeystone', {
+  'ks_version': [ 'uint', [ uintPtr, uintPtr ] ],
+  'ks_arch_supported': [ 'bool', [ ks_arch ] ],
+  'ks_open': [ ks_err, [ ks_arch, 'int', ks_enginePtrPtr ] ],
+  'ks_close': [ ks_err, [ ks_enginePtr ] ],
+  'ks_errno': [ 'int', [ ks_enginePtr ] ],
+  'ks_strerror': [ 'string', [ ks_err ] ],
+  'ks_option': [ ks_err, [ ks_enginePtr, ks_opt_type, 'size_t' ] ],
+  'ks_asm': [ 'int', [ ks_enginePtr, 'string', 'uint64', ucharPtrPtr, size_tPtr, size_tPtr ] ],
+  'ks_free': [ 'void', [ 'pointer' ] ]
+})
+
+function KsError(message, errno, count) {
+  this.message = message
+  this.errno = errno
+  this.count = count
+}
+
+function Ks(arch, mode) {
+  var _ks = ref.alloc(ks_enginePtr),
+      err = Keystone.ks_open(arch, mode, _ks)
+
+  if (err !== consts.ERR_OK) {
+    this._ks = null
+    throw new KsError('Error: failed on ks_open()')
+  }
+
+  this._ks = _ks.deref()
+  
+
+  this.__defineGetter__('errno', function() {
+    return Keystone.ks_errno(this._ks)
+  })
+
+  this.__defineSetter__('syntax', function(value) {
+    this.set_option(consts.OPT_SYNTAX, value)
+  })
+}
+
+Ks.prototype.asm = function(code, addr) {
+  var encoding = ref.alloc('uchar *'),
+      size = ref.alloc('size_t'),
+      count = ref.alloc('size_t'),
+      err, msg 
+
+  if (Keystone.ks_asm(this._ks, code, addr || 0, encoding, size, count) !== consts.ERR_OK) {
+    err = this.errno
+    msg = Keystone.ks_strerror(err)
+    throw new KsError(msg, err, count.deref())
+  }
+
+  return {
+    encoding: ref.reinterpret(encoding.deref(), size.deref(), 0),
+    count: count.deref()
+  }
+}
+
+Ks.prototype.close = function() {
+  Keystone.ks_close(this._ks)
+  this._ks = null
+}
+
+Ks.prototype.set_option = function(type, value) {
+  var err = Keystone.ks_option(this._ks, type, value)
+  if (err != consts.ERR_OK) {
+    throw new KsError(Keystone.ks_strerror(err), err)
+  }
+}
+
+module.exports.Ks = Ks
+
+module.exports.is_arch_supported = function(arch) {
+  return Keystone.ks_arch_supported(arch)
+}
+
+module.exports.__defineGetter__('version', function() {
+  var version = Keystone.ks_version(null, null)
+  return {
+    major: version >> 8,
+    minor: version & 255
+  }
+})
+extend(module.exports, consts)
diff --git a/bindings/nodejs/package.json b/bindings/nodejs/package.json
new file mode 100644
index 0000000..bf93ec9
--- /dev/null
+++ b/bindings/nodejs/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "keystone",
+  "version": "1.0.0",
+  "description": "Keystone assembler engine",
+  "homepage": "http://www.keystone-engine.org",
+  "main": "index.js",
+  "dependencies": {
+    "ffi": "^2.0.0",
+    "ref": "^1.3.2"
+  },
+  "devDependencies": {},
+  "scripts": {
+    "prepublish": "cd .. && python const_generator.py nodejs",
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "Ingmar Steen <iksteen@gmail.com>",
+  "license": "MIT"
+}
diff --git a/bindings/nodejs/sample.js b/bindings/nodejs/sample.js
new file mode 100644
index 0000000..ddfa545
--- /dev/null
+++ b/bindings/nodejs/sample.js
@@ -0,0 +1,27 @@
+var keystone = require('.')  // Or: require('keystone') if you have installed it
+
+console.log('Using keystone ' + keystone.version.major + '.' + keystone.version.minor)
+
+var ks, assembly, result
+
+// Check if architecture is supported
+if (! keystone.is_arch_supported(keystone.ARCH_X86)) {
+  throw 'Warning: X86 architecture not supported by keystone.'
+}
+
+// Create a new Keystone instance for X86 64bit
+ks = new keystone.Ks(keystone.ARCH_X86, keystone.MODE_64)
+
+// Assemble some instructions
+assembly = 'inc rcx; dec rbx'
+result = ks.asm(assembly)
+console.log('"' + assembly + '"', ':', result.encoding)
+
+// Change syntax, assemble some more instructions
+assembly = 'lea rax, [label1]\nnop\nnop\nlabel1:'
+ks.syntax = keystone.OPT_SYNTAX_NASM
+result = ks.asm(assembly)
+console.log('"' + assembly.replace(/\n/g, '; ') + '"', ':', result.encoding)
+
+// Close Keystone instance to free resources
+ks.close()
-- 
GitLab